From 1950e5960203003a84e1546b186f8517b96bc2bc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 6 Jun 2026 07:12:28 +0000 Subject: [PATCH 1/3] chore(deps): Bump the all-dependencies group with 14 updates Bumps AWSSDK.SecretsManager from 4.0.4.24 to 4.0.5 Bumps JD.SemanticKernel.Connectors.ClaudeCode from 1.0.31 to 1.0.32 Bumps JD.SemanticKernel.Connectors.GitHubCopilot from 0.1.56 to 0.1.59 Bumps JD.SemanticKernel.Connectors.OpenAICodex from 0.1.27 to 0.1.28 Bumps JD.SemanticKernel.Extensions from 0.1.95 to 0.1.99 Bumps JD.SemanticKernel.Extensions.Mcp from 0.1.95 to 0.1.99 Bumps Microsoft.SemanticKernel from 1.76.0 to 1.77.0 Bumps Microsoft.SemanticKernel.Abstractions from 1.76.0 to 1.77.0 Bumps Microsoft.SemanticKernel.Connectors.AzureOpenAI from 1.76.0 to 1.77.0 Bumps Microsoft.SemanticKernel.Connectors.OpenAI from 1.76.0 to 1.77.0 Bumps MudBlazor from 8.5.1 to 9.5.0 Bumps SixLabors.ImageSharp from 3.1.12 to 4.0.0 Bumps TinyBDD.Xunit from 0.19.21 to 0.19.22 Bumps WorkflowFramework from 1.0.0 to 1.0.1 --- updated-dependencies: - dependency-name: AWSSDK.SecretsManager dependency-version: 4.0.5 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: all-dependencies - dependency-name: JD.SemanticKernel.Connectors.ClaudeCode dependency-version: 1.0.32 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: all-dependencies - dependency-name: Microsoft.SemanticKernel dependency-version: 1.77.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: all-dependencies - dependency-name: Microsoft.SemanticKernel.Connectors.AzureOpenAI dependency-version: 1.77.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: all-dependencies - dependency-name: Microsoft.SemanticKernel.Connectors.OpenAI dependency-version: 1.77.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: all-dependencies - dependency-name: JD.SemanticKernel.Connectors.GitHubCopilot dependency-version: 0.1.59 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: all-dependencies - dependency-name: JD.SemanticKernel.Connectors.OpenAICodex dependency-version: 0.1.28 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: all-dependencies - dependency-name: JD.SemanticKernel.Extensions dependency-version: 0.1.99 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: all-dependencies - dependency-name: JD.SemanticKernel.Extensions.Mcp dependency-version: 0.1.99 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: all-dependencies - dependency-name: Microsoft.SemanticKernel.Abstractions dependency-version: 1.77.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: all-dependencies - dependency-name: MudBlazor dependency-version: 9.5.0 dependency-type: direct:production update-type: version-update:semver-major dependency-group: all-dependencies - dependency-name: SixLabors.ImageSharp dependency-version: 4.0.0 dependency-type: direct:production update-type: version-update:semver-major dependency-group: all-dependencies - dependency-name: TinyBDD.Xunit dependency-version: 0.19.22 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: all-dependencies - dependency-name: WorkflowFramework dependency-version: 1.0.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: all-dependencies ... Signed-off-by: dependabot[bot] --- Directory.Packages.props | 216 +++++++++++++++++++-------------------- 1 file changed, 108 insertions(+), 108 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 7425e23c..68f23ad5 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,108 +1,108 @@ - - - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 2db2ecbc598cf4588bb1fc97339a052eb73450a9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 10 Jun 2026 14:46:32 +0000 Subject: [PATCH 2/3] chore: start CI failure investigation --- .github/workflows/ci.yml | 726 ++++++++++++------------ .github/workflows/codeql.yml | 74 +-- .github/workflows/containers.yml | 262 ++++----- .github/workflows/dependency-review.yml | 50 +- .github/workflows/docs.yml | 196 +++---- .github/workflows/pr-validation.yml | 438 +++++++------- Directory.Packages.props | 216 +++---- 7 files changed, 981 insertions(+), 981 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1cf6b4c6..31c7bec0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,363 +1,363 @@ -name: CI - -on: - push: - branches: [main] - pull_request: - branches: [main] - workflow_dispatch: - -env: - DOTNET_NOLOGO: true - TEST_RESULTS_DIR: artifacts/test-results - -jobs: - pr-checks: - if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: write - steps: - - name: Checkout - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - - name: Setup .NET - uses: actions/setup-dotnet@v5 - with: - global-json-file: global.json - - - name: Restore - run: dotnet restore JD.AI.slnx - - - name: Build (Release) - run: > - dotnet build JD.AI.slnx - --configuration Release - --no-restore - /p:ContinuousIntegrationBuild=true - - - name: Verify formatting - run: > - dotnet format JD.AI.slnx - --severity warn - --verify-no-changes - - - name: Test with coverage - timeout-minutes: 15 - run: > - dotnet test JD.AI.slnx - --configuration Release - --no-build - --results-directory ${{ env.TEST_RESULTS_DIR }} - --filter "Category!=Integration&Category!=MlModel&Category!=FlakyEnvironment&Category!=E2E" - --collect:"XPlat Code Coverage" - --blame-hang-timeout 5m - -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=cobertura - DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Include="[JD.AI*]*" - DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Exclude="[*Tests]*" - - - name: Install ReportGenerator - if: always() - run: dotnet tool update -g dotnet-reportgenerator-globaltool --version 5.5.4 - - - name: Combine coverage reports - if: always() - shell: bash - run: | - REPORTS=$(find "${TEST_RESULTS_DIR}" -type f -name "coverage.cobertura.xml" 2>/dev/null | tr '\n' ';') - if [ -z "$REPORTS" ] || [ "$REPORTS" = ";" ]; then - echo "No coverage files found — skipping report generation." - exit 0 - fi - reportgenerator \ - -reports:"$REPORTS" \ - -targetdir:"coverage-report" \ - -reporttypes:"HtmlInline;Cobertura;TextSummary;Badges" \ - -assemblyfilters:"+JD.AI*;-*Tests*" \ - -filefilters:"-**/*.Tests/*;-**/*Tests*/**" - echo "COVERAGE_SUMMARY<> $GITHUB_ENV - awk '/^[^ ]/ && NR>1 {exit} {print}' coverage-report/Summary.txt >> $GITHUB_ENV - echo "EOF" >> $GITHUB_ENV - - - name: Upload coverage report - if: always() - uses: actions/upload-artifact@v7 - with: - name: coverage-report - path: coverage-report - - - name: Upload to Codecov - if: always() - continue-on-error: true - uses: codecov/codecov-action@v6 - with: - files: coverage-report/Cobertura.xml - disable_search: true - handle_no_reports_found: true - fail_ci_if_error: false - - - name: Enforce coverage floor - if: always() - shell: bash - env: - MIN_LINE_COVERAGE: "60" - run: | - if [ ! -f coverage-report/Cobertura.xml ]; then - echo "Coverage report missing." - exit 1 - fi - python - <<'PY' - import os - import xml.etree.ElementTree as ET - - min_cov = float(os.environ["MIN_LINE_COVERAGE"]) - rate = float(ET.parse("coverage-report/Cobertura.xml").getroot().attrib["line-rate"]) * 100 - if rate < min_cov: - raise SystemExit(f"Coverage {rate:.2f}% is below {min_cov:.2f}%") - print(f"Coverage {rate:.2f}% meets threshold {min_cov:.2f}%") - PY - - - name: Add coverage summary to PR - if: always() && github.event_name == 'pull_request' - uses: marocchino/sticky-pull-request-comment@v3 - with: - recreate: true - message: | - ## Code Coverage - ``` - ${{ env.COVERAGE_SUMMARY }} - ``` - - release: - if: github.event_name == 'push' && github.ref == 'refs/heads/main' - runs-on: ubuntu-latest - permissions: - contents: write - packages: write - steps: - - name: Checkout - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - - name: Setup .NET - uses: actions/setup-dotnet@v5 - with: - global-json-file: global.json - - - name: Restore - run: dotnet restore JD.AI.slnx - - - name: Determine version (NBGV) - id: nbgv - uses: dotnet/nbgv@81f7c98786b56d9fa30c96223e7147d9bb582ff7 # node24 (unreleased past v0.5.1; pin SHA until v0.5.2 ships) - with: - setAllVars: true - - - name: Build (Release) - run: > - dotnet build JD.AI.slnx - --configuration Release - --no-restore - /p:ContinuousIntegrationBuild=true - - - name: Test (Release) - timeout-minutes: 15 - run: > - dotnet test JD.AI.slnx - --configuration Release - --no-build - --results-directory ${{ env.TEST_RESULTS_DIR }} - --filter "Category!=Integration&Category!=MlModel&Category!=FlakyEnvironment&Category!=E2E" - --collect:"XPlat Code Coverage" - --blame-hang-timeout 5m - -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=cobertura - DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Include="[JD.AI*]*" - DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Exclude="[*Tests]*" - - - name: Install ReportGenerator - if: always() - run: dotnet tool update -g dotnet-reportgenerator-globaltool --version 5.5.4 - - - name: Combine coverage reports - if: always() - shell: bash - run: | - REPORTS=$(find "${TEST_RESULTS_DIR}" -type f -name "coverage.cobertura.xml" 2>/dev/null | tr '\n' ';') - if [ -z "$REPORTS" ] || [ "$REPORTS" = ";" ]; then - echo "No coverage files found — skipping report generation." - exit 0 - fi - reportgenerator \ - -reports:"$REPORTS" \ - -targetdir:"coverage-report" \ - -reporttypes:"HtmlInline;Cobertura;TextSummary;Badges" \ - -assemblyfilters:"+JD.AI*;-*Tests*" \ - -filefilters:"-**/*.Tests/*;-**/*Tests*/**" - - - name: Upload coverage report - if: always() - uses: actions/upload-artifact@v7 - with: - name: coverage-report - path: coverage-report - - - name: Upload to Codecov - if: always() - uses: codecov/codecov-action@v6 - with: - files: coverage-report/Cobertura.xml - token: ${{ secrets.CODECOV_TOKEN }} - disable_search: true - handle_no_reports_found: true - fail_ci_if_error: false - - - name: Enforce coverage floor - if: always() - shell: bash - env: - MIN_LINE_COVERAGE: "60" - run: | - if [ ! -f coverage-report/Cobertura.xml ]; then - echo "Coverage report missing." - exit 1 - fi - python - <<'PY' - import os - import xml.etree.ElementTree as ET - - min_cov = float(os.environ["MIN_LINE_COVERAGE"]) - rate = float(ET.parse("coverage-report/Cobertura.xml").getroot().attrib["line-rate"]) * 100 - if rate < min_cov: - raise SystemExit(f"Coverage {rate:.2f}% is below {min_cov:.2f}%") - print(f"Coverage {rate:.2f}% meets threshold {min_cov:.2f}%") - PY - - - name: Pack - run: > - dotnet pack JD.AI.slnx - --configuration Release - --no-build - --output ./artifacts - /p:ContinuousIntegrationBuild=true - - - name: Upload packages - uses: actions/upload-artifact@v7 - with: - name: nuget-packages - path: ./artifacts/*.nupkg - - - name: Push to NuGet.org - env: - NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} - run: | - if [ -n "$NUGET_API_KEY" ]; then - dotnet nuget push ./artifacts/*.nupkg \ - --api-key "$NUGET_API_KEY" \ - --source https://api.nuget.org/v3/index.json \ - --skip-duplicate - else - echo "Skipping NuGet.org push: API key not set." - fi - - - name: Push to GitHub Packages - run: | - dotnet nuget push "./artifacts/*.nupkg" \ - --source "https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json" \ - --api-key "${{ secrets.GITHUB_TOKEN }}" \ - --skip-duplicate - - - name: Create git tag - shell: bash - run: | - set -euo pipefail - TAG="v${NBGV_NuGetPackageVersion}" - if git rev-parse "refs/tags/$TAG" >/dev/null 2>&1; then - echo "Tag $TAG already exists — skipping." - else - git tag "$TAG" - git push origin "$TAG" - echo "Created tag $TAG" - fi - - - name: Create GitHub Release - uses: softprops/action-gh-release@v3 - with: - tag_name: v${{ env.NBGV_NuGetPackageVersion }} - name: Release v${{ env.NBGV_NuGetPackageVersion }} - files: | - ./artifacts/*.nupkg - generate_release_notes: true - outputs: - version: ${{ env.NBGV_NuGetPackageVersion }} - - publish-binaries: - needs: release - if: github.event_name == 'push' && github.ref == 'refs/heads/main' - strategy: - fail-fast: false - matrix: - include: - - rid: win-x64 - os: windows-latest - archive: zip - - rid: win-arm64 - os: windows-latest - archive: zip - - rid: linux-x64 - os: ubuntu-latest - archive: tar.gz - - rid: linux-arm64 - os: ubuntu-latest - archive: tar.gz - - rid: osx-x64 - os: macos-latest - archive: tar.gz - - rid: osx-arm64 - os: macos-latest - archive: tar.gz - runs-on: ${{ matrix.os }} - permissions: - contents: write - steps: - - name: Checkout - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - - name: Setup .NET - uses: actions/setup-dotnet@v5 - with: - global-json-file: global.json - - - name: Publish self-contained binary - env: - GITHUB_ACTIONS: "false" - run: > - dotnet publish src/JD.AI/JD.AI.csproj - --configuration Release - --runtime ${{ matrix.rid }} - --self-contained - -p:PublishSingleFile=true - -p:IncludeNativeLibrariesForSelfExtract=true - -p:ContinuousIntegrationBuild=true - --output ./publish - - - name: Archive (zip) - if: matrix.archive == 'zip' - shell: pwsh - run: Compress-Archive -Path ./publish/* -DestinationPath ./jdai-${{ matrix.rid }}.zip - - - name: Archive (tar.gz) - if: matrix.archive == 'tar.gz' - run: tar -czf ./jdai-${{ matrix.rid }}.tar.gz -C ./publish . - - - name: Upload to GitHub Release - uses: softprops/action-gh-release@v3 - with: - tag_name: v${{ needs.release.outputs.version }} - files: | - ./jdai-${{ matrix.rid }}.${{ matrix.archive }} +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + workflow_dispatch: + +env: + DOTNET_NOLOGO: true + TEST_RESULTS_DIR: artifacts/test-results + +jobs: + pr-checks: + if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Setup .NET + uses: actions/setup-dotnet@v5 + with: + global-json-file: global.json + + - name: Restore + run: dotnet restore JD.AI.slnx + + - name: Build (Release) + run: > + dotnet build JD.AI.slnx + --configuration Release + --no-restore + /p:ContinuousIntegrationBuild=true + + - name: Verify formatting + run: > + dotnet format JD.AI.slnx + --severity warn + --verify-no-changes + + - name: Test with coverage + timeout-minutes: 15 + run: > + dotnet test JD.AI.slnx + --configuration Release + --no-build + --results-directory ${{ env.TEST_RESULTS_DIR }} + --filter "Category!=Integration&Category!=MlModel&Category!=FlakyEnvironment&Category!=E2E" + --collect:"XPlat Code Coverage" + --blame-hang-timeout 5m + -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=cobertura + DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Include="[JD.AI*]*" + DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Exclude="[*Tests]*" + + - name: Install ReportGenerator + if: always() + run: dotnet tool update -g dotnet-reportgenerator-globaltool --version 5.5.4 + + - name: Combine coverage reports + if: always() + shell: bash + run: | + REPORTS=$(find "${TEST_RESULTS_DIR}" -type f -name "coverage.cobertura.xml" 2>/dev/null | tr '\n' ';') + if [ -z "$REPORTS" ] || [ "$REPORTS" = ";" ]; then + echo "No coverage files found — skipping report generation." + exit 0 + fi + reportgenerator \ + -reports:"$REPORTS" \ + -targetdir:"coverage-report" \ + -reporttypes:"HtmlInline;Cobertura;TextSummary;Badges" \ + -assemblyfilters:"+JD.AI*;-*Tests*" \ + -filefilters:"-**/*.Tests/*;-**/*Tests*/**" + echo "COVERAGE_SUMMARY<> $GITHUB_ENV + awk '/^[^ ]/ && NR>1 {exit} {print}' coverage-report/Summary.txt >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + + - name: Upload coverage report + if: always() + uses: actions/upload-artifact@v7 + with: + name: coverage-report + path: coverage-report + + - name: Upload to Codecov + if: always() + continue-on-error: true + uses: codecov/codecov-action@v6 + with: + files: coverage-report/Cobertura.xml + disable_search: true + handle_no_reports_found: true + fail_ci_if_error: false + + - name: Enforce coverage floor + if: always() + shell: bash + env: + MIN_LINE_COVERAGE: "60" + run: | + if [ ! -f coverage-report/Cobertura.xml ]; then + echo "Coverage report missing." + exit 1 + fi + python - <<'PY' + import os + import xml.etree.ElementTree as ET + + min_cov = float(os.environ["MIN_LINE_COVERAGE"]) + rate = float(ET.parse("coverage-report/Cobertura.xml").getroot().attrib["line-rate"]) * 100 + if rate < min_cov: + raise SystemExit(f"Coverage {rate:.2f}% is below {min_cov:.2f}%") + print(f"Coverage {rate:.2f}% meets threshold {min_cov:.2f}%") + PY + + - name: Add coverage summary to PR + if: always() && github.event_name == 'pull_request' + uses: marocchino/sticky-pull-request-comment@v3 + with: + recreate: true + message: | + ## Code Coverage + ``` + ${{ env.COVERAGE_SUMMARY }} + ``` + + release: + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + runs-on: ubuntu-latest + permissions: + contents: write + packages: write + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Setup .NET + uses: actions/setup-dotnet@v5 + with: + global-json-file: global.json + + - name: Restore + run: dotnet restore JD.AI.slnx + + - name: Determine version (NBGV) + id: nbgv + uses: dotnet/nbgv@81f7c98786b56d9fa30c96223e7147d9bb582ff7 # node24 (unreleased past v0.5.1; pin SHA until v0.5.2 ships) + with: + setAllVars: true + + - name: Build (Release) + run: > + dotnet build JD.AI.slnx + --configuration Release + --no-restore + /p:ContinuousIntegrationBuild=true + + - name: Test (Release) + timeout-minutes: 15 + run: > + dotnet test JD.AI.slnx + --configuration Release + --no-build + --results-directory ${{ env.TEST_RESULTS_DIR }} + --filter "Category!=Integration&Category!=MlModel&Category!=FlakyEnvironment&Category!=E2E" + --collect:"XPlat Code Coverage" + --blame-hang-timeout 5m + -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=cobertura + DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Include="[JD.AI*]*" + DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Exclude="[*Tests]*" + + - name: Install ReportGenerator + if: always() + run: dotnet tool update -g dotnet-reportgenerator-globaltool --version 5.5.4 + + - name: Combine coverage reports + if: always() + shell: bash + run: | + REPORTS=$(find "${TEST_RESULTS_DIR}" -type f -name "coverage.cobertura.xml" 2>/dev/null | tr '\n' ';') + if [ -z "$REPORTS" ] || [ "$REPORTS" = ";" ]; then + echo "No coverage files found — skipping report generation." + exit 0 + fi + reportgenerator \ + -reports:"$REPORTS" \ + -targetdir:"coverage-report" \ + -reporttypes:"HtmlInline;Cobertura;TextSummary;Badges" \ + -assemblyfilters:"+JD.AI*;-*Tests*" \ + -filefilters:"-**/*.Tests/*;-**/*Tests*/**" + + - name: Upload coverage report + if: always() + uses: actions/upload-artifact@v7 + with: + name: coverage-report + path: coverage-report + + - name: Upload to Codecov + if: always() + uses: codecov/codecov-action@v6 + with: + files: coverage-report/Cobertura.xml + token: ${{ secrets.CODECOV_TOKEN }} + disable_search: true + handle_no_reports_found: true + fail_ci_if_error: false + + - name: Enforce coverage floor + if: always() + shell: bash + env: + MIN_LINE_COVERAGE: "60" + run: | + if [ ! -f coverage-report/Cobertura.xml ]; then + echo "Coverage report missing." + exit 1 + fi + python - <<'PY' + import os + import xml.etree.ElementTree as ET + + min_cov = float(os.environ["MIN_LINE_COVERAGE"]) + rate = float(ET.parse("coverage-report/Cobertura.xml").getroot().attrib["line-rate"]) * 100 + if rate < min_cov: + raise SystemExit(f"Coverage {rate:.2f}% is below {min_cov:.2f}%") + print(f"Coverage {rate:.2f}% meets threshold {min_cov:.2f}%") + PY + + - name: Pack + run: > + dotnet pack JD.AI.slnx + --configuration Release + --no-build + --output ./artifacts + /p:ContinuousIntegrationBuild=true + + - name: Upload packages + uses: actions/upload-artifact@v7 + with: + name: nuget-packages + path: ./artifacts/*.nupkg + + - name: Push to NuGet.org + env: + NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} + run: | + if [ -n "$NUGET_API_KEY" ]; then + dotnet nuget push ./artifacts/*.nupkg \ + --api-key "$NUGET_API_KEY" \ + --source https://api.nuget.org/v3/index.json \ + --skip-duplicate + else + echo "Skipping NuGet.org push: API key not set." + fi + + - name: Push to GitHub Packages + run: | + dotnet nuget push "./artifacts/*.nupkg" \ + --source "https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json" \ + --api-key "${{ secrets.GITHUB_TOKEN }}" \ + --skip-duplicate + + - name: Create git tag + shell: bash + run: | + set -euo pipefail + TAG="v${NBGV_NuGetPackageVersion}" + if git rev-parse "refs/tags/$TAG" >/dev/null 2>&1; then + echo "Tag $TAG already exists — skipping." + else + git tag "$TAG" + git push origin "$TAG" + echo "Created tag $TAG" + fi + + - name: Create GitHub Release + uses: softprops/action-gh-release@v3 + with: + tag_name: v${{ env.NBGV_NuGetPackageVersion }} + name: Release v${{ env.NBGV_NuGetPackageVersion }} + files: | + ./artifacts/*.nupkg + generate_release_notes: true + outputs: + version: ${{ env.NBGV_NuGetPackageVersion }} + + publish-binaries: + needs: release + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + strategy: + fail-fast: false + matrix: + include: + - rid: win-x64 + os: windows-latest + archive: zip + - rid: win-arm64 + os: windows-latest + archive: zip + - rid: linux-x64 + os: ubuntu-latest + archive: tar.gz + - rid: linux-arm64 + os: ubuntu-latest + archive: tar.gz + - rid: osx-x64 + os: macos-latest + archive: tar.gz + - rid: osx-arm64 + os: macos-latest + archive: tar.gz + runs-on: ${{ matrix.os }} + permissions: + contents: write + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Setup .NET + uses: actions/setup-dotnet@v5 + with: + global-json-file: global.json + + - name: Publish self-contained binary + env: + GITHUB_ACTIONS: "false" + run: > + dotnet publish src/JD.AI/JD.AI.csproj + --configuration Release + --runtime ${{ matrix.rid }} + --self-contained + -p:PublishSingleFile=true + -p:IncludeNativeLibrariesForSelfExtract=true + -p:ContinuousIntegrationBuild=true + --output ./publish + + - name: Archive (zip) + if: matrix.archive == 'zip' + shell: pwsh + run: Compress-Archive -Path ./publish/* -DestinationPath ./jdai-${{ matrix.rid }}.zip + + - name: Archive (tar.gz) + if: matrix.archive == 'tar.gz' + run: tar -czf ./jdai-${{ matrix.rid }}.tar.gz -C ./publish . + + - name: Upload to GitHub Release + uses: softprops/action-gh-release@v3 + with: + tag_name: v${{ needs.release.outputs.version }} + files: | + ./jdai-${{ matrix.rid }}.${{ matrix.archive }} diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 5a0a41d4..ba567637 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -1,37 +1,37 @@ -name: CodeQL - -on: - push: - branches: [main] - pull_request: - branches: [main] - schedule: - - cron: '0 6 * * 1' - -permissions: - security-events: write - contents: read - -jobs: - analyze: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - - name: Setup .NET - uses: actions/setup-dotnet@v5 - with: - global-json-file: global.json - - - name: Initialize CodeQL - uses: github/codeql-action/init@v4.36.0 - with: - languages: csharp - - - name: Build - run: dotnet build --configuration Release - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v4.36.0 +name: CodeQL + +on: + push: + branches: [main] + pull_request: + branches: [main] + schedule: + - cron: '0 6 * * 1' + +permissions: + security-events: write + contents: read + +jobs: + analyze: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Setup .NET + uses: actions/setup-dotnet@v5 + with: + global-json-file: global.json + + - name: Initialize CodeQL + uses: github/codeql-action/init@v4.36.0 + with: + languages: csharp + + - name: Build + run: dotnet build --configuration Release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v4.36.0 diff --git a/.github/workflows/containers.yml b/.github/workflows/containers.yml index 84b01066..7d0b9731 100644 --- a/.github/workflows/containers.yml +++ b/.github/workflows/containers.yml @@ -1,131 +1,131 @@ -name: Container Images - -on: - push: - branches: [main] - tags: - - "v*" - workflow_dispatch: - -env: - REGISTRY: ghcr.io - -jobs: - publish: - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - id-token: write - attestations: write - strategy: - fail-fast: false - matrix: - include: - - component: gateway - dockerfile: deploy/docker/Dockerfile.gateway - - component: daemon - dockerfile: deploy/docker/Dockerfile.daemon - - component: tui - dockerfile: deploy/docker/Dockerfile.tui - steps: - - name: Checkout - uses: actions/checkout@v6 - - - name: Set up Docker Buildx - shell: bash - run: | - docker buildx version - docker buildx create --name jdai-builder --use || docker buildx use jdai-builder - docker buildx inspect --bootstrap - - - name: Normalize image name - id: image - shell: bash - run: | - owner="${GITHUB_REPOSITORY_OWNER,,}" - echo "name=${{ env.REGISTRY }}/${owner}/jd.ai-${{ matrix.component }}" >> "$GITHUB_OUTPUT" - - - name: Login to GHCR - uses: docker/login-action@v4.2.0 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract metadata - id: meta - uses: docker/metadata-action@v6 - with: - images: ${{ steps.image.outputs.name }} - tags: | - type=sha - type=raw,value=latest,enable={{is_default_branch}} - type=ref,event=tag - - - name: Build and push - id: build - uses: docker/build-push-action@v7 - with: - context: . - file: ${{ matrix.dockerfile }} - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max - - - name: Generate SBOM - uses: anchore/sbom-action@v0 - with: - image: ${{ steps.image.outputs.name }}@${{ steps.build.outputs.digest }} - format: spdx-json - output-file: sbom-${{ matrix.component }}.spdx.json - - - name: Upload SBOM artifact - uses: actions/upload-artifact@v7 - with: - name: sbom-${{ matrix.component }} - path: sbom-${{ matrix.component }}.spdx.json - - - name: Attest SBOM to image - uses: actions/attest-sbom@v4 - continue-on-error: true - with: - subject-name: ${{ steps.image.outputs.name }} - subject-digest: ${{ steps.build.outputs.digest }} - sbom-path: sbom-${{ matrix.component }}.spdx.json - - helm-lint: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v6 - - - name: Install Helm - shell: bash - run: | - set -euo pipefail - HELM_VERSION="v3.19.0" - ARCHIVE="helm-${HELM_VERSION}-linux-amd64.tar.gz" - URL="https://get.helm.sh/${ARCHIVE}" - for attempt in 1 2 3; do - if curl -fsSL --retry 3 --retry-delay 2 "$URL" -o "$ARCHIVE"; then - break - fi - if [ "$attempt" -eq 3 ]; then - echo "Failed to download Helm from $URL after retries." - exit 1 - fi - sleep $((attempt * 5)) - done - tar -xzf "$ARCHIVE" - sudo mv linux-amd64/helm /usr/local/bin/helm - helm version --short - - - name: Helm lint - run: helm lint deploy/helm/jdai - - - name: Helm template (dry-run) - run: helm template jdai deploy/helm/jdai --debug > /dev/null - +name: Container Images + +on: + push: + branches: [main] + tags: + - "v*" + workflow_dispatch: + +env: + REGISTRY: ghcr.io + +jobs: + publish: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + id-token: write + attestations: write + strategy: + fail-fast: false + matrix: + include: + - component: gateway + dockerfile: deploy/docker/Dockerfile.gateway + - component: daemon + dockerfile: deploy/docker/Dockerfile.daemon + - component: tui + dockerfile: deploy/docker/Dockerfile.tui + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Set up Docker Buildx + shell: bash + run: | + docker buildx version + docker buildx create --name jdai-builder --use || docker buildx use jdai-builder + docker buildx inspect --bootstrap + + - name: Normalize image name + id: image + shell: bash + run: | + owner="${GITHUB_REPOSITORY_OWNER,,}" + echo "name=${{ env.REGISTRY }}/${owner}/jd.ai-${{ matrix.component }}" >> "$GITHUB_OUTPUT" + + - name: Login to GHCR + uses: docker/login-action@v4.2.0 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v6 + with: + images: ${{ steps.image.outputs.name }} + tags: | + type=sha + type=raw,value=latest,enable={{is_default_branch}} + type=ref,event=tag + + - name: Build and push + id: build + uses: docker/build-push-action@v7 + with: + context: . + file: ${{ matrix.dockerfile }} + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Generate SBOM + uses: anchore/sbom-action@v0 + with: + image: ${{ steps.image.outputs.name }}@${{ steps.build.outputs.digest }} + format: spdx-json + output-file: sbom-${{ matrix.component }}.spdx.json + + - name: Upload SBOM artifact + uses: actions/upload-artifact@v7 + with: + name: sbom-${{ matrix.component }} + path: sbom-${{ matrix.component }}.spdx.json + + - name: Attest SBOM to image + uses: actions/attest-sbom@v4 + continue-on-error: true + with: + subject-name: ${{ steps.image.outputs.name }} + subject-digest: ${{ steps.build.outputs.digest }} + sbom-path: sbom-${{ matrix.component }}.spdx.json + + helm-lint: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Install Helm + shell: bash + run: | + set -euo pipefail + HELM_VERSION="v3.19.0" + ARCHIVE="helm-${HELM_VERSION}-linux-amd64.tar.gz" + URL="https://get.helm.sh/${ARCHIVE}" + for attempt in 1 2 3; do + if curl -fsSL --retry 3 --retry-delay 2 "$URL" -o "$ARCHIVE"; then + break + fi + if [ "$attempt" -eq 3 ]; then + echo "Failed to download Helm from $URL after retries." + exit 1 + fi + sleep $((attempt * 5)) + done + tar -xzf "$ARCHIVE" + sudo mv linux-amd64/helm /usr/local/bin/helm + helm version --short + + - name: Helm lint + run: helm lint deploy/helm/jdai + + - name: Helm template (dry-run) + run: helm template jdai deploy/helm/jdai --debug > /dev/null + diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index b3e49362..6e48cb62 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -1,25 +1,25 @@ -name: Dependency Review - -on: - pull_request: - branches: [main] - -permissions: - contents: read - pull-requests: write - -jobs: - dependency-review: - name: Dependency Review - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - name: Checkout - uses: actions/checkout@v6 - - - name: Dependency Review - uses: actions/dependency-review-action@v5 - with: - fail-on-severity: moderate - deny-licenses: GPL-2.0, GPL-3.0, AGPL-3.0 - comment-summary-in-pr: always +name: Dependency Review + +on: + pull_request: + branches: [main] + +permissions: + contents: read + pull-requests: write + +jobs: + dependency-review: + name: Dependency Review + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Dependency Review + uses: actions/dependency-review-action@v5 + with: + fail-on-severity: moderate + deny-licenses: GPL-2.0, GPL-3.0, AGPL-3.0 + comment-summary-in-pr: always diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index fafb3413..67d3f681 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -1,98 +1,98 @@ -name: Publish docs - -on: - push: - branches: [main] - pull_request: - branches: [main] - workflow_dispatch: - -permissions: - actions: read - pages: write - id-token: write - pull-requests: write - -concurrency: - group: pages - cancel-in-progress: false - -jobs: - validate-docs: - if: github.event_name == 'pull_request' - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - - name: Setup .NET - uses: actions/setup-dotnet@v5 - with: - global-json-file: global.json - - - name: Restore - run: dotnet restore JD.AI.slnx - - - name: Install docfx - run: dotnet tool update -g docfx - - - name: Build documentation - run: docfx docs/docfx.json - - - name: Validate output - run: | - if [ ! -d "docs/_site" ]; then - echo "::error::Documentation build did not create _site directory" - exit 1 - fi - HTML_COUNT=$(find docs/_site -name "*.html" | wc -l) - echo "Generated $HTML_COUNT HTML files" - if [ "$HTML_COUNT" -lt 1 ]; then - echo "::error::No HTML files were generated" - exit 1 - fi - echo "::notice::Documentation validation passed! Generated $HTML_COUNT pages." - - - name: Upload preview - uses: actions/upload-artifact@v7 - with: - name: documentation-preview - path: docs/_site - retention-days: 7 - - publish-docs: - if: (github.event_name == 'push' && github.ref == 'refs/heads/main') || github.event_name == 'workflow_dispatch' - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - - name: Setup .NET - uses: actions/setup-dotnet@v5 - with: - global-json-file: global.json - - - name: Restore - run: dotnet restore JD.AI.slnx - - - name: Install docfx - run: dotnet tool update -g docfx - - - name: Build documentation - run: docfx docs/docfx.json - - - name: Upload artifact - uses: actions/upload-pages-artifact@v5 - with: - path: docs/_site - - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v5 +name: Publish docs + +on: + push: + branches: [main] + pull_request: + branches: [main] + workflow_dispatch: + +permissions: + actions: read + pages: write + id-token: write + pull-requests: write + +concurrency: + group: pages + cancel-in-progress: false + +jobs: + validate-docs: + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Setup .NET + uses: actions/setup-dotnet@v5 + with: + global-json-file: global.json + + - name: Restore + run: dotnet restore JD.AI.slnx + + - name: Install docfx + run: dotnet tool update -g docfx + + - name: Build documentation + run: docfx docs/docfx.json + + - name: Validate output + run: | + if [ ! -d "docs/_site" ]; then + echo "::error::Documentation build did not create _site directory" + exit 1 + fi + HTML_COUNT=$(find docs/_site -name "*.html" | wc -l) + echo "Generated $HTML_COUNT HTML files" + if [ "$HTML_COUNT" -lt 1 ]; then + echo "::error::No HTML files were generated" + exit 1 + fi + echo "::notice::Documentation validation passed! Generated $HTML_COUNT pages." + + - name: Upload preview + uses: actions/upload-artifact@v7 + with: + name: documentation-preview + path: docs/_site + retention-days: 7 + + publish-docs: + if: (github.event_name == 'push' && github.ref == 'refs/heads/main') || github.event_name == 'workflow_dispatch' + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Setup .NET + uses: actions/setup-dotnet@v5 + with: + global-json-file: global.json + + - name: Restore + run: dotnet restore JD.AI.slnx + + - name: Install docfx + run: dotnet tool update -g docfx + + - name: Build documentation + run: docfx docs/docfx.json + + - name: Upload artifact + uses: actions/upload-pages-artifact@v5 + with: + path: docs/_site + + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v5 diff --git a/.github/workflows/pr-validation.yml b/.github/workflows/pr-validation.yml index e4dcb564..d031e440 100644 --- a/.github/workflows/pr-validation.yml +++ b/.github/workflows/pr-validation.yml @@ -1,219 +1,219 @@ -name: PR Validation - -on: - pull_request: - branches: [main] - paths-ignore: - - '**.md' - - 'docs/**' - - '.vscode/**' - - '.editorconfig' - workflow_dispatch: - -env: - DOTNET_NOLOGO: true - TEST_RESULTS_DIR: artifacts/test-results - -permissions: - contents: read - pull-requests: write - checks: write - -jobs: - validate-pr: - name: Validate PR - runs-on: ubuntu-latest - timeout-minutes: 30 - steps: - - name: Checkout - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - - name: Setup .NET - uses: actions/setup-dotnet@v5 - with: - global-json-file: global.json - - - name: Determine version (NBGV) - id: nbgv - uses: dotnet/nbgv@81f7c98786b56d9fa30c96223e7147d9bb582ff7 # node24 (unreleased past v0.5.1; pin SHA until v0.5.2 ships) - with: - setAllVars: true - - - name: Restore - run: dotnet restore JD.AI.slnx - - - name: Build - run: > - dotnet build JD.AI.slnx - --configuration Release - --no-restore - /p:ContinuousIntegrationBuild=true - /p:Deterministic=true - - - name: Verify formatting - run: > - dotnet format JD.AI.slnx - --severity warn - --verify-no-changes - - - name: Run tests with coverage - run: > - dotnet test JD.AI.slnx - --configuration Release - --no-build - --results-directory ${{ env.TEST_RESULTS_DIR }} - --filter "Category!=Integration&Category!=MlModel&Category!=FlakyEnvironment&Category!=E2E" - --verbosity normal - --logger trx - --collect:"XPlat Code Coverage" - --blame-hang-timeout 5m - -- - DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=cobertura - DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Include="[JD.AI*]*" - DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Exclude="[*Tests]*" - - - name: Install ReportGenerator - if: always() - run: dotnet tool update -g dotnet-reportgenerator-globaltool --version 5.5.4 - - - name: Generate coverage report - if: always() - shell: bash - run: | - REPORTS=$(find "${TEST_RESULTS_DIR}" -type f -name "coverage.cobertura.xml" 2>/dev/null | tr '\n' ';') - if [ -z "$REPORTS" ] || [ "$REPORTS" = ";" ]; then - echo "No coverage files found — skipping report generation." - exit 0 - fi - reportgenerator \ - -reports:"$REPORTS" \ - -targetdir:"coverage-report" \ - -reporttypes:"HtmlInline;Cobertura;TextSummary;Badges" \ - -assemblyfilters:"+JD.AI*;-*Tests*" \ - -filefilters:"-**/*.Tests/*;-**/*Tests*/**" - - - name: Upload coverage report - if: always() - uses: actions/upload-artifact@v7 - with: - name: coverage-report - path: coverage-report - - - name: Enforce coverage floor - if: always() - shell: bash - env: - MIN_LINE_COVERAGE: "60" - run: | - if [ ! -f coverage-report/Cobertura.xml ]; then - echo "Coverage report missing." - exit 1 - fi - python - <<'PY' - import os - import xml.etree.ElementTree as ET - - min_cov = float(os.environ["MIN_LINE_COVERAGE"]) - rate = float(ET.parse("coverage-report/Cobertura.xml").getroot().attrib["line-rate"]) * 100 - if rate < min_cov: - raise SystemExit(f"Coverage {rate:.2f}% is below {min_cov:.2f}%") - print(f"Coverage {rate:.2f}% meets threshold {min_cov:.2f}%") - PY - - - name: Coverage gate — check changed source files - if: always() && github.event_name == 'pull_request' - shell: bash - env: - GH_TOKEN: ${{ github.token }} - PR_NUMBER: ${{ github.event.pull_request.number }} - run: | - # Determine if this PR has a coverage-override label - LABELS=$(gh pr view "$PR_NUMBER" --json labels --jq '[.labels[].name] | join(",")' 2>/dev/null || echo "") - if echo "$LABELS" | grep -q "coverage-override"; then - echo "ℹ️ coverage-override label found — skipping coverage gate." - exit 0 - fi - - # Get changed runtime source files (not tests) - CHANGED_SRC=$(git diff --name-only origin/main...HEAD -- 'src/**/*.cs' 2>/dev/null || \ - git diff --name-only HEAD~1...HEAD -- 'src/**/*.cs' 2>/dev/null || echo "") - - if [ -z "$CHANGED_SRC" ]; then - echo "✅ No runtime source files changed — coverage gate passed." - exit 0 - fi - - echo "📁 Changed source files:" - echo "$CHANGED_SRC" - - # Check if test files were also changed - CHANGED_TESTS=$(git diff --name-only origin/main...HEAD -- 'tests/**/*.cs' 2>/dev/null || \ - git diff --name-only HEAD~1...HEAD -- 'tests/**/*.cs' 2>/dev/null || echo "") - - if [ -z "$CHANGED_TESTS" ]; then - echo "" - echo "⚠️ Runtime source files were modified but no test files changed." - echo " Please add or update tests for the changed code, or add the" - echo " 'coverage-override' label to this PR to bypass this check." - echo "" - echo "Changed files requiring tests:" - echo "$CHANGED_SRC" - exit 1 - fi - - echo "✅ Test files updated alongside source changes — coverage gate passed." - echo "Changed test files:" - echo "$CHANGED_TESTS" - - - name: Publish test results - uses: EnricoMi/publish-unit-test-result-action@v2 - if: always() - with: - files: | - ${{ env.TEST_RESULTS_DIR }}/**/*.trx - check_name: Test Results - - - name: Dry-run NuGet packaging - run: | - echo "📦 Performing dry-run of NuGet packaging..." - mkdir -p ./dry-run-packages - dotnet pack JD.AI.slnx \ - --configuration Release \ - --no-build \ - --output ./dry-run-packages \ - /p:ContinuousIntegrationBuild=true - PACKAGE_COUNT=$(ls -1 ./dry-run-packages/*.nupkg 2>/dev/null | wc -l) - if [ "$PACKAGE_COUNT" -eq 0 ]; then - echo "❌ No packages were created" - exit 1 - fi - echo "📦 Packaged files:" - ls -lh ./dry-run-packages/ - - - name: Upload dry-run packages - uses: actions/upload-artifact@v7 - with: - name: dry-run-packages - path: ./dry-run-packages/*.nupkg - retention-days: 7 - - - name: PR Summary - if: always() - run: | - echo "## 🎯 PR Validation Summary" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "✅ **Version**: $NBGV_NuGetPackageVersion" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### 📦 Packages" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - if ls ./dry-run-packages/*.nupkg 1> /dev/null 2>&1; then - echo "The following packages will be created on merge:" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - ls -1 ./dry-run-packages/*.nupkg | xargs -n1 basename >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - else - echo "⚠️ No packages were created during the dry-run." >> $GITHUB_STEP_SUMMARY - fi +name: PR Validation + +on: + pull_request: + branches: [main] + paths-ignore: + - '**.md' + - 'docs/**' + - '.vscode/**' + - '.editorconfig' + workflow_dispatch: + +env: + DOTNET_NOLOGO: true + TEST_RESULTS_DIR: artifacts/test-results + +permissions: + contents: read + pull-requests: write + checks: write + +jobs: + validate-pr: + name: Validate PR + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Setup .NET + uses: actions/setup-dotnet@v5 + with: + global-json-file: global.json + + - name: Determine version (NBGV) + id: nbgv + uses: dotnet/nbgv@81f7c98786b56d9fa30c96223e7147d9bb582ff7 # node24 (unreleased past v0.5.1; pin SHA until v0.5.2 ships) + with: + setAllVars: true + + - name: Restore + run: dotnet restore JD.AI.slnx + + - name: Build + run: > + dotnet build JD.AI.slnx + --configuration Release + --no-restore + /p:ContinuousIntegrationBuild=true + /p:Deterministic=true + + - name: Verify formatting + run: > + dotnet format JD.AI.slnx + --severity warn + --verify-no-changes + + - name: Run tests with coverage + run: > + dotnet test JD.AI.slnx + --configuration Release + --no-build + --results-directory ${{ env.TEST_RESULTS_DIR }} + --filter "Category!=Integration&Category!=MlModel&Category!=FlakyEnvironment&Category!=E2E" + --verbosity normal + --logger trx + --collect:"XPlat Code Coverage" + --blame-hang-timeout 5m + -- + DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=cobertura + DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Include="[JD.AI*]*" + DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Exclude="[*Tests]*" + + - name: Install ReportGenerator + if: always() + run: dotnet tool update -g dotnet-reportgenerator-globaltool --version 5.5.4 + + - name: Generate coverage report + if: always() + shell: bash + run: | + REPORTS=$(find "${TEST_RESULTS_DIR}" -type f -name "coverage.cobertura.xml" 2>/dev/null | tr '\n' ';') + if [ -z "$REPORTS" ] || [ "$REPORTS" = ";" ]; then + echo "No coverage files found — skipping report generation." + exit 0 + fi + reportgenerator \ + -reports:"$REPORTS" \ + -targetdir:"coverage-report" \ + -reporttypes:"HtmlInline;Cobertura;TextSummary;Badges" \ + -assemblyfilters:"+JD.AI*;-*Tests*" \ + -filefilters:"-**/*.Tests/*;-**/*Tests*/**" + + - name: Upload coverage report + if: always() + uses: actions/upload-artifact@v7 + with: + name: coverage-report + path: coverage-report + + - name: Enforce coverage floor + if: always() + shell: bash + env: + MIN_LINE_COVERAGE: "60" + run: | + if [ ! -f coverage-report/Cobertura.xml ]; then + echo "Coverage report missing." + exit 1 + fi + python - <<'PY' + import os + import xml.etree.ElementTree as ET + + min_cov = float(os.environ["MIN_LINE_COVERAGE"]) + rate = float(ET.parse("coverage-report/Cobertura.xml").getroot().attrib["line-rate"]) * 100 + if rate < min_cov: + raise SystemExit(f"Coverage {rate:.2f}% is below {min_cov:.2f}%") + print(f"Coverage {rate:.2f}% meets threshold {min_cov:.2f}%") + PY + + - name: Coverage gate — check changed source files + if: always() && github.event_name == 'pull_request' + shell: bash + env: + GH_TOKEN: ${{ github.token }} + PR_NUMBER: ${{ github.event.pull_request.number }} + run: | + # Determine if this PR has a coverage-override label + LABELS=$(gh pr view "$PR_NUMBER" --json labels --jq '[.labels[].name] | join(",")' 2>/dev/null || echo "") + if echo "$LABELS" | grep -q "coverage-override"; then + echo "ℹ️ coverage-override label found — skipping coverage gate." + exit 0 + fi + + # Get changed runtime source files (not tests) + CHANGED_SRC=$(git diff --name-only origin/main...HEAD -- 'src/**/*.cs' 2>/dev/null || \ + git diff --name-only HEAD~1...HEAD -- 'src/**/*.cs' 2>/dev/null || echo "") + + if [ -z "$CHANGED_SRC" ]; then + echo "✅ No runtime source files changed — coverage gate passed." + exit 0 + fi + + echo "📁 Changed source files:" + echo "$CHANGED_SRC" + + # Check if test files were also changed + CHANGED_TESTS=$(git diff --name-only origin/main...HEAD -- 'tests/**/*.cs' 2>/dev/null || \ + git diff --name-only HEAD~1...HEAD -- 'tests/**/*.cs' 2>/dev/null || echo "") + + if [ -z "$CHANGED_TESTS" ]; then + echo "" + echo "⚠️ Runtime source files were modified but no test files changed." + echo " Please add or update tests for the changed code, or add the" + echo " 'coverage-override' label to this PR to bypass this check." + echo "" + echo "Changed files requiring tests:" + echo "$CHANGED_SRC" + exit 1 + fi + + echo "✅ Test files updated alongside source changes — coverage gate passed." + echo "Changed test files:" + echo "$CHANGED_TESTS" + + - name: Publish test results + uses: EnricoMi/publish-unit-test-result-action@v2 + if: always() + with: + files: | + ${{ env.TEST_RESULTS_DIR }}/**/*.trx + check_name: Test Results + + - name: Dry-run NuGet packaging + run: | + echo "📦 Performing dry-run of NuGet packaging..." + mkdir -p ./dry-run-packages + dotnet pack JD.AI.slnx \ + --configuration Release \ + --no-build \ + --output ./dry-run-packages \ + /p:ContinuousIntegrationBuild=true + PACKAGE_COUNT=$(ls -1 ./dry-run-packages/*.nupkg 2>/dev/null | wc -l) + if [ "$PACKAGE_COUNT" -eq 0 ]; then + echo "❌ No packages were created" + exit 1 + fi + echo "📦 Packaged files:" + ls -lh ./dry-run-packages/ + + - name: Upload dry-run packages + uses: actions/upload-artifact@v7 + with: + name: dry-run-packages + path: ./dry-run-packages/*.nupkg + retention-days: 7 + + - name: PR Summary + if: always() + run: | + echo "## 🎯 PR Validation Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "✅ **Version**: $NBGV_NuGetPackageVersion" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### 📦 Packages" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + if ls ./dry-run-packages/*.nupkg 1> /dev/null 2>&1; then + echo "The following packages will be created on merge:" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + ls -1 ./dry-run-packages/*.nupkg | xargs -n1 basename >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + else + echo "⚠️ No packages were created during the dry-run." >> $GITHUB_STEP_SUMMARY + fi diff --git a/Directory.Packages.props b/Directory.Packages.props index 68f23ad5..de15f14f 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,108 +1,108 @@ - - - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 9f4ddf78cd610ae04790604f3cce3804a56aff16 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 10 Jun 2026 15:01:33 +0000 Subject: [PATCH 3/3] fix: pin dependencies compatible with CI build --- Directory.Packages.props | 4 ++-- src/JD.AI.Core/JD.AI.Core.csproj | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index de15f14f..02619af4 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -35,7 +35,7 @@ - + @@ -69,7 +69,7 @@ - + diff --git a/src/JD.AI.Core/JD.AI.Core.csproj b/src/JD.AI.Core/JD.AI.Core.csproj index d646a89c..8c2a5ecc 100644 --- a/src/JD.AI.Core/JD.AI.Core.csproj +++ b/src/JD.AI.Core/JD.AI.Core.csproj @@ -9,7 +9,8 @@ - $(NoWarn);NU5118;NU5100;NU5104;CA2227;CA1002;CA1859;MA0016 + + $(NoWarn);NU5118;NU5100;NU5104;CA2227;CA1002;CA1859;CA1724;MA0016