CoolFace
Datasetpublic

basant307/AI_Governance_Project

sourceHugging Faceapache-2.0updated 2mo agoView on Hugging Face
0likes48downloads
ci.yml627 linesDownload Raw Back to workflows
1# .github/workflows/ci.yml2 3name: 'Qwen Code CI'4 5on:6  # No `push` trigger: every job here is gated to pull_request / merge_group, so7  # a push to `main` ran nothing (CodeQL was the last push job and moved to its8  # own scheduled codeql.yml). The merge queue validates the merged tree before9  # it lands, so there is nothing left to run on the post-merge push.10  pull_request:11    branches:12      - 'main'13      - 'release/**'14  merge_group:15  workflow_dispatch:16    inputs:17      branch_ref:18        description: 'Branch to run on'19        required: true20        default: 'main'21        type: 'string'22 23concurrency:24  group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}'25  cancel-in-progress: |-26    ${{ github.ref != 'refs/heads/main' && !startsWith(github.ref, 'refs/heads/release/') }}27 28permissions:29  checks: 'write'30  contents: 'read'31  statuses: 'write'32 33defaults:34  run:35    shell: 'bash'36 37env:38  ACTIONLINT_VERSION: '1.7.12'39  SHELLCHECK_VERSION: '0.11.0'40  YAMLLINT_VERSION: '1.35.1'41 42jobs:43  classify_pr:44    name: 'Classify PR'45    if: "${{ github.event_name == 'pull_request' || github.event_name == 'merge_group' }}"46    # Gate runs on ECS for in-repo PRs and for the merge queue (which runs in the47    # base-repo context), else a busy hosted pool delays it and blocks the48    # ECS-bound jobs. The kill-switch is read here, so flipping it reverts49    # everything to hosted.50    runs-on: '${{ (vars.MAINTAINER_ECS_RUNNER_DISABLED != ''true'' && (github.event.pull_request.head.repo.full_name == github.repository || github.event_name == ''merge_group'')) && fromJSON(''["self-hosted", "linux", "x64", "ecs-qwen"]'') || fromJSON(''["ubuntu-latest"]'') }}'51    continue-on-error: true52    outputs:53      skip_ci: '${{ steps.release_sync.outputs.skip_ci }}'54      ubuntu_runner: '${{ steps.pick_runner.outputs.ubuntu_runner }}'55    steps:56      - name: 'Detect release version-sync PR'57        id: 'release_sync'58        env:59          # Repository variables can override these defaults if release naming60          # or the CI bot account changes.61          HEAD_REPO: "${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name || '' }}"62          HEAD_REF: "${{ github.event_name == 'pull_request' && github.head_ref || '' }}"63          PR_TITLE: "${{ github.event_name == 'pull_request' && github.event.pull_request.title || '' }}"64          RELEASE_SYNC_HEAD_PREFIX: "${{ vars.RELEASE_SYNC_HEAD_PREFIX || 'release/' }}"65          RELEASE_SYNC_TITLE_PREFIX: "${{ vars.RELEASE_SYNC_TITLE_PREFIX || 'chore(release):' }}"66          RELEASE_SYNC_ACTOR: "${{ vars.RELEASE_SYNC_ACTOR || 'qwen-code-ci-bot' }}"67        run: |-68          skip_ci=false69          repo_match=false70          actor_match=false71          head_match=false72          title_match=false73          [[ "${HEAD_REPO}" == "${GITHUB_REPOSITORY}" ]] && repo_match=true74          [[ "${GITHUB_ACTOR}" == "${RELEASE_SYNC_ACTOR}" ]] && actor_match=true75          [[ "${HEAD_REF}" == "${RELEASE_SYNC_HEAD_PREFIX}"* ]] && head_match=true76          [[ "${PR_TITLE}" == "${RELEASE_SYNC_TITLE_PREFIX}"* ]] && title_match=true77 78          if [[ "${GITHUB_EVENT_NAME}" == "pull_request" &&79                "${repo_match}" == "true" &&80                "${actor_match}" == "true" &&81                "${head_match}" == "true" &&82                "${title_match}" == "true" ]]; then83            skip_ci=true84            echo "Release sync PR detected: actor=${GITHUB_ACTOR}, head_ref=${HEAD_REF}, title=${PR_TITLE}"85          else86            echo "Not a release sync PR: event=${GITHUB_EVENT_NAME}, actor=${GITHUB_ACTOR}, expected_actor=${RELEASE_SYNC_ACTOR}, repo_match=${repo_match}, head_match=${head_match}, title_match=${title_match}"87          fi88 89          echo "skip_ci=${skip_ci}" >> "${GITHUB_OUTPUT}"90          echo "skip_ci=${skip_ci}"91 92      # In-repo PR (head branch in this repo => author has write access) and the93      # merge queue (base-repo context) run the Linux jobs on ECS; fork PRs stay94      # hosted. Disable via repo var MAINTAINER_ECS_RUNNER_DISABLED=true.95      - name: 'Select Linux runner'96        id: 'pick_runner'97        env:98          SAME_REPO: '${{ github.event.pull_request.head.repo.full_name == github.repository }}'99          ECS_DISABLED: '${{ vars.MAINTAINER_ECS_RUNNER_DISABLED }}'100          EVENT_NAME: '${{ github.event_name }}'101        run: |-102          ubuntu_runner='["ubuntu-latest"]'103          if [[ "${ECS_DISABLED}" != "true" && ( "${SAME_REPO}" == "true" || "${EVENT_NAME}" == "merge_group" ) ]]; then104            ubuntu_runner='["self-hosted", "linux", "x64", "ecs-qwen"]'105          fi106          echo "ubuntu_runner=${ubuntu_runner}" >> "${GITHUB_OUTPUT}"107          echo "Selected Linux runner: ${ubuntu_runner}"108 109  #110  # Test: Node111  #112  test:113    name: 'Test (ubuntu-latest, Node 22.x)'114    needs: 'classify_pr'115    # Stay running on release-sync PRs so the required Test contexts still116    # report; the per-step skip_ci guards below make them no-op (pass) there.117    # Not on push: the merge queue already tested the merged tree, so a118    # post-merge re-run on `main` would be redundant.119    if: "${{ !cancelled() && github.event_name != 'push' }}"120    runs-on: '${{ fromJSON(needs.classify_pr.outputs.ubuntu_runner || ''["ubuntu-latest"]'') }}'121    timeout-minutes: 60122    outputs:123      ci_profile: '${{ steps.ci_profile.outputs.ci_profile }}'124    permissions:125      contents: 'read'126      checks: 'write'127      pull-requests: 'write'128    steps:129      # On PRs, check out refs/pull/N/head (the immutable PR head, published the130      # instant the branch is pushed) instead of github.ref. github.ref is the131      # merge ref (refs/pull/N/merge), which GitHub rebuilds asynchronously and132      # can serve stale for minutes after a push, repeatedly flaking this gate.133      # Merge queue refs are ephemeral; check out the event head SHA directly so134      # slow hosted runners do not fail after the queue branch is removed.135      # Non-PR/non-queue events keep github.ref.136      - name: 'Checkout'137        id: 'checkout'138        if: "${{ needs.classify_pr.outputs.skip_ci != 'true' }}"139        uses: 'actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10'  # v6.0.3140        with:141          ref: "${{ github.event.inputs.branch_ref || (github.event_name == 'pull_request' && format('refs/pull/{0}/head', github.event.pull_request.number)) || (github.event_name == 'merge_group' && github.event.merge_group.head_sha) || github.ref }}"142          # Shallow: nothing here walks git history (the verify guard below checks143          # head.sha == HEAD, schema/tests touch only the working tree). On the144          # in-repo ECS runner a full-history clone is the heaviest transfer and145          # chokes the squid egress proxy, flaking checkout. depth 1 is enough.146          fetch-depth: 1147 148      # Guard against a stale checkout (e.g. a caching egress proxy serving an old149      # ref) silently testing the wrong tree. Cheap: one merge-base, sub-second.150      # Also runs in the merge queue — now that the queue's Ubuntu checkout is on151      # ECS/squid, a wrong-tree pass would merge bad code.152      - name: 'Verify checkout includes expected head commit'153        if: "${{ needs.classify_pr.outputs.skip_ci != 'true' && (github.event_name == 'pull_request' || github.event_name == 'merge_group') }}"154        env:155          EXPECTED_SHA: "${{ github.event_name == 'merge_group' && github.event.merge_group.head_sha || github.event.pull_request.head.sha }}"156        run: |-157          if ! git merge-base --is-ancestor "${EXPECTED_SHA}" HEAD; then158            echo "::error::Checked out ref does not contain expected head ${EXPECTED_SHA}."159            git log --oneline --decorate -5160            exit 1161          fi162 163      - name: 'Classify CI profile'164        id: 'ci_profile'165        if: "${{ needs.classify_pr.outputs.skip_ci != 'true' }}"166        env:167          GH_TOKEN: '${{ github.token }}'168          PR_NUMBER: "${{ github.event_name == 'pull_request' && github.event.pull_request.number || '' }}"169          IS_SAME_REPO_PR: "${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository }}"170        run: |-171          profile=full172          if [[ "${GITHUB_EVENT_NAME}" == "pull_request" && -n "${PR_NUMBER}" ]]; then173            if [[ "${IS_SAME_REPO_PR}" == "true" ]]; then174              changed_files="${RUNNER_TEMP}/changed-files.jsonl"175              if gh api --paginate "repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}/files" --jq '.[] | {filename, status, previous_filename}' > "${changed_files}"; then176                if ! profile="$(node .github/scripts/ci/classify-profile.mjs "${changed_files}")"; then177                  echo "::error::CI profile classifier exited non-zero; running full CI."178                  profile=full179                fi180              else181                echo "::warning::Unable to list PR changed files; running full CI."182              fi183            else184              echo "Fork PR detected; running full CI."185            fi186          fi187          echo "ci_profile=${profile}" >> "${GITHUB_OUTPUT}"188          echo "Selected CI profile: ${profile}"189 190      - name: 'Docs-only CI'191        if: "${{ needs.classify_pr.outputs.skip_ci != 'true' && steps.ci_profile.outputs.ci_profile == 'docs_only' }}"192        run: 'echo "Docs-only change; full CI skipped."'193 194      - name: 'GitHub CI helper checks'195        if: "${{ needs.classify_pr.outputs.skip_ci != 'true' && steps.ci_profile.outputs.ci_profile == 'github_ci_only' }}"196        timeout-minutes: 5197        run: |-198          # Keep this path dependency-free; script formatting is checked when those files hit full CI.199          node scripts/lint.js --setup200          node scripts/lint.js --actionlint201          node scripts/lint.js --yamllint202          node --test .github/scripts/pr-safety-precheck.test.mjs .github/scripts/ci/classify-profile.test.mjs .github/scripts/resolve-sandbox-image.test.mjs203 204      # Self-hosted can't reach nodejs.org reliably; reuse the machine's Node.205      - name: 'Set up Node.js 22.x (hosted)'206        if: "${{ needs.classify_pr.outputs.skip_ci != 'true' && steps.ci_profile.outputs.ci_profile == 'full' && runner.environment == 'github-hosted' }}"207        uses: 'actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e' # v6.4.0208        with:209          node-version: '22.x'210          cache: 'npm'211          cache-dependency-path: 'package-lock.json'212          registry-url: 'https://registry.npmjs.org/'213 214      - name: 'Use pre-installed Node.js (self-hosted)'215        if: "${{ needs.classify_pr.outputs.skip_ci != 'true' && steps.ci_profile.outputs.ci_profile == 'full' && runner.environment == 'self-hosted' }}"216        run: |-217          if ! command -v node >/dev/null 2>&1; then218            echo "::error::Node.js is not on PATH for this self-hosted runner. Provision Node 22.x or set the MAINTAINER_ECS_RUNNER_DISABLED repository variable to 'true' to route PRs back to hosted runners."219            exit 1220          fi221          echo "Using pre-installed Node $(node -v) / npm $(npm -v)"222          if [[ "$(node -p 'process.versions.node.split(".")[0]')" != "22" ]]; then223            echo "::warning::Expected Node 22.x but found $(node -v); tests will run against the runner's Node."224          fi225 226      - name: 'Configure persistent npm cache (self-hosted)'227        if: "${{ needs.classify_pr.outputs.skip_ci != 'true' && steps.ci_profile.outputs.ci_profile == 'full' && runner.environment == 'self-hosted' }}"228        run: |-229          cache_dir="${HOME}/.cache/qwen-code/npm"230          mkdir -p "${cache_dir}"231          echo "NPM_CONFIG_CACHE=${cache_dir}" >> "${GITHUB_ENV}"232          echo "Using persistent npm cache at ${cache_dir}"233          du -sh "${cache_dir}" 2>/dev/null || true234 235      - name: 'Configure npm for rate limiting'236        if: "${{ needs.classify_pr.outputs.skip_ci != 'true' && steps.ci_profile.outputs.ci_profile == 'full' }}"237        run: |-238          npm config set fetch-retry-mintimeout 20000239          npm config set fetch-retry-maxtimeout 120000240          npm config set fetch-retries 5241          npm config set fetch-timeout 300000242 243      - name: 'Install dependencies'244        if: "${{ needs.classify_pr.outputs.skip_ci != 'true' && steps.ci_profile.outputs.ci_profile == 'full' }}"245        run: |-246          npm ci --prefer-offline --no-audit --progress=false247 248      - name: 'Report npm cache usage (self-hosted)'249        if: "${{ always() && needs.classify_pr.outputs.skip_ci != 'true' && steps.ci_profile.outputs.ci_profile == 'full' && runner.environment == 'self-hosted' }}"250        run: |-251          cache_dir="${NPM_CONFIG_CACHE:-$(npm config get cache)}"252          echo "npm cache: ${cache_dir}"253          du -sh "${cache_dir}" 2>/dev/null || true254 255      - name: 'Audit critical runtime dependencies'256        if: "${{ needs.classify_pr.outputs.skip_ci != 'true' && steps.ci_profile.outputs.ci_profile == 'full' }}"257        run: 'npm run audit:runtime:critical'258 259      - name: 'Check lockfile'260        if: "${{ needs.classify_pr.outputs.skip_ci != 'true' && steps.ci_profile.outputs.ci_profile == 'full' }}"261        run: 'npm run check:lockfile'262 263      - name: 'Check desktop workspace isolation'264        if: "${{ needs.classify_pr.outputs.skip_ci != 'true' && steps.ci_profile.outputs.ci_profile == 'full' }}"265        run: 'npm run check:desktop-isolation'266 267      - name: 'Install linters'268        if: "${{ needs.classify_pr.outputs.skip_ci != 'true' && steps.ci_profile.outputs.ci_profile == 'full' }}"269        run: 'node scripts/lint.js --setup'270 271      - name: 'Run ESLint'272        if: "${{ needs.classify_pr.outputs.skip_ci != 'true' && steps.ci_profile.outputs.ci_profile == 'full' }}"273        run: 'node scripts/lint.js --eslint'274 275      - name: 'Run actionlint'276        if: "${{ needs.classify_pr.outputs.skip_ci != 'true' && steps.ci_profile.outputs.ci_profile == 'full' }}"277        timeout-minutes: 5278        run: 'node scripts/lint.js --actionlint'279 280      - name: 'Run shellcheck'281        if: "${{ needs.classify_pr.outputs.skip_ci != 'true' && steps.ci_profile.outputs.ci_profile == 'full' }}"282        run: 'node scripts/lint.js --shellcheck'283 284      - name: 'Run yamllint'285        if: "${{ needs.classify_pr.outputs.skip_ci != 'true' && steps.ci_profile.outputs.ci_profile == 'full' }}"286        run: 'node scripts/lint.js --yamllint'287 288      - name: 'Run Prettier'289        if: "${{ needs.classify_pr.outputs.skip_ci != 'true' && steps.ci_profile.outputs.ci_profile == 'full' }}"290        run: 'node scripts/lint.js --prettier'291 292      - name: 'Run sensitive keyword linter'293        if: "${{ needs.classify_pr.outputs.skip_ci != 'true' && steps.ci_profile.outputs.ci_profile == 'full' }}"294        run: 'node scripts/lint.js --sensitive-keywords'295 296      - name: 'Run i18n check'297        if: "${{ needs.classify_pr.outputs.skip_ci != 'true' && steps.ci_profile.outputs.ci_profile == 'full' }}"298        run: 'npm run check-i18n'299 300      - name: 'Generate settings schema'301        if: "${{ needs.classify_pr.outputs.skip_ci != 'true' && steps.ci_profile.outputs.ci_profile == 'full' }}"302        run: 'npm run generate:settings-schema'303 304      - name: 'Check settings schema is up-to-date'305        if: "${{ needs.classify_pr.outputs.skip_ci != 'true' && steps.ci_profile.outputs.ci_profile == 'full' }}"306        run: |-307          if [[ -n $(git status --porcelain packages/vscode-ide-companion/schemas/settings.schema.json) ]]; then308            echo "Error: settings.schema.json is out of date."309            echo "Please run: npm run generate:settings-schema"310            echo "Then commit the updated schema file."311            git diff packages/vscode-ide-companion/schemas/settings.schema.json312            exit 1313          fi314          echo "Settings schema is up-to-date"315 316      # Keep this Linux-only PR gate explicit. macOS/Windows merge-queue jobs run317      # npm run test:ci only, so they intentionally do not repeat this318      # platform-independent bundle closure check.319      - name: 'Check serve fast-path bundle closure'320        if: "${{ needs.classify_pr.outputs.skip_ci != 'true' && steps.ci_profile.outputs.ci_profile == 'full' }}"321        run: 'npm run check:serve-fast-path-bundle'322 323      - name: 'Run tests and generate reports'324        id: 'unit_tests'325        if: "${{ needs.classify_pr.outputs.skip_ci != 'true' && steps.ci_profile.outputs.ci_profile == 'full' }}"326        env:327          NO_COLOR: true328          HOME: '${{ runner.temp }}/qwen-ci-home'329          USERPROFILE: '${{ runner.temp }}/qwen-ci-home'330          OPENAI_API_KEY: ''331          DASHSCOPE_API_KEY: ''332          QWEN_API_KEY: ''333          GEMINI_API_KEY: ''334          QWEN_DEFAULT_AUTH_TYPE: ''335        run: |-336          node -e "const fs = require('node:fs'); for (const key of ['HOME', 'USERPROFILE']) { const dir = process.env[key]; if (dir) fs.mkdirSync(dir, { recursive: true }); }"337          npm run test:ci338 339      - name: 'Run no-AK integration smoke tests'340        if: "${{ needs.classify_pr.outputs.skip_ci != 'true' && steps.ci_profile.outputs.ci_profile == 'full' && github.event_name == 'pull_request' }}"341        run: 'npm run test:integration:no-ak:sandbox:none'342 343      - name: 'Publish Test Report (for non-forks)'344        if: |-345          ${{ always() && needs.classify_pr.outputs.skip_ci != 'true' && steps.ci_profile.outputs.ci_profile == 'full' && steps.unit_tests.outcome != 'skipped' && (github.event.pull_request.head.repo.full_name == github.repository) }}346        uses: 'dorny/test-reporter@a43b3a5f7366b97d083190328d2c652e1a8b6aa2' # ratchet:dorny/test-reporter@v3347        with:348          name: 'Test Results (ubuntu-latest, Node 22.x)'349          path: 'packages/*/junit.xml'350          reporter: 'java-junit'351          fail-on-error: 'false'352 353      - name: 'Upload Test Results Artifact (for forks)'354        if: |-355          ${{ always() && needs.classify_pr.outputs.skip_ci != 'true' && steps.ci_profile.outputs.ci_profile == 'full' && (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) }}356        uses: 'actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a' # v7.0.1357        with:358          name: 'test-results-fork-22.x-ubuntu-latest'359          path: 'packages/*/junit.xml'360 361      - name: 'Upload coverage reports'362        if: "${{ always() && needs.classify_pr.outputs.skip_ci != 'true' && steps.ci_profile.outputs.ci_profile == 'full' }}"363        uses: 'actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a' # v7.0.1364        with:365          name: 'coverage-reports-22.x-ubuntu-latest'366          path: 'packages/*/coverage'367 368  # macOS/Windows: slowest/costliest runners, rare platform regressions — run369  # only in the merge queue. Skipped on PR (ubuntu is the fast PR signal) and on370  # push (the queue already tested the merged tree, so a post-merge re-run is371  # redundant). Two named jobs, not a matrix: a skipped matrix job reports one372  # collapsed check name, never the per-OS required contexts, so PRs would sit373  # "Expected" forever and never enter the queue. A skipped named job reports374  # under its exact name and satisfies the required check (same as the375  # Integration Tests job).376  test_macos:377    name: 'Test (macos-latest, Node 22.x)'378    needs: 'classify_pr'379    if: "${{ !cancelled() && github.event_name == 'merge_group' }}"380    runs-on: 'macos-latest'381    permissions:382      contents: 'read'383    steps:384      # See the Ubuntu gate's checkout: PRs use the immutable refs/pull/N/head385      # and merge queue uses the event head SHA.386      - name: 'Checkout'387        id: 'checkout'388        if: "${{ needs.classify_pr.outputs.skip_ci != 'true' }}"389        uses: 'actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10'  # v6.0.3390        with:391          ref: "${{ github.event.inputs.branch_ref || (github.event_name == 'pull_request' && format('refs/pull/{0}/head', github.event.pull_request.number)) || (github.event_name == 'merge_group' && github.event.merge_group.head_sha) || github.ref }}"392 393      - name: 'Set up Node.js 22.x'394        if: "${{ needs.classify_pr.outputs.skip_ci != 'true' }}"395        uses: 'actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e' # v6.4.0396        with:397          node-version: '22.x'398          cache: 'npm'399          cache-dependency-path: 'package-lock.json'400          registry-url: 'https://registry.npmjs.org/'401 402      - name: 'Configure npm for rate limiting'403        if: "${{ needs.classify_pr.outputs.skip_ci != 'true' }}"404        run: |-405          npm config set fetch-retry-mintimeout 20000406          npm config set fetch-retry-maxtimeout 120000407          npm config set fetch-retries 5408          npm config set fetch-timeout 300000409 410      - name: 'Install dependencies'411        if: "${{ needs.classify_pr.outputs.skip_ci != 'true' }}"412        run: |-413          npm ci --prefer-offline --no-audit --progress=false414 415      - name: 'Run tests and generate reports'416        if: "${{ needs.classify_pr.outputs.skip_ci != 'true' }}"417        env:418          NO_COLOR: true419          HOME: '${{ runner.temp }}/qwen-ci-home'420          USERPROFILE: '${{ runner.temp }}/qwen-ci-home'421          OPENAI_API_KEY: ''422          DASHSCOPE_API_KEY: ''423          QWEN_API_KEY: ''424          GEMINI_API_KEY: ''425          QWEN_DEFAULT_AUTH_TYPE: ''426        run: |-427          node -e "const fs = require('node:fs'); for (const key of ['HOME', 'USERPROFILE']) { const dir = process.env[key]; if (dir) fs.mkdirSync(dir, { recursive: true }); }"428          npm run test:ci429 430  # Windows counterpart of test_macos (see that job's note). Runner is431  # windows-2022; the check name keeps the windows-latest label so it matches432  # the required-status-check context.433  test_windows:434    name: 'Test (windows-latest, Node 22.x)'435    needs: 'classify_pr'436    if: "${{ !cancelled() && github.event_name == 'merge_group' }}"437    runs-on: 'windows-2022'438    permissions:439      contents: 'read'440    steps:441      - name: 'Checkout'442        id: 'checkout'443        if: "${{ needs.classify_pr.outputs.skip_ci != 'true' }}"444        uses: 'actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10'  # v6.0.3445        with:446          ref: "${{ github.event.inputs.branch_ref || (github.event_name == 'pull_request' && format('refs/pull/{0}/head', github.event.pull_request.number)) || (github.event_name == 'merge_group' && github.event.merge_group.head_sha) || github.ref }}"447 448      - name: 'Set up Node.js 22.x'449        if: "${{ needs.classify_pr.outputs.skip_ci != 'true' }}"450        uses: 'actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e' # v6.4.0451        with:452          node-version: '22.x'453          cache: 'npm'454          cache-dependency-path: 'package-lock.json'455          registry-url: 'https://registry.npmjs.org/'456 457      - name: 'Configure npm for rate limiting'458        if: "${{ needs.classify_pr.outputs.skip_ci != 'true' }}"459        run: |-460          npm config set fetch-retry-mintimeout 20000461          npm config set fetch-retry-maxtimeout 120000462          npm config set fetch-retries 5463          npm config set fetch-timeout 300000464 465      - name: 'Install dependencies'466        if: "${{ needs.classify_pr.outputs.skip_ci != 'true' }}"467        run: |-468          npm ci --prefer-offline --no-audit --progress=false469 470      - name: 'Run tests and generate reports'471        if: "${{ needs.classify_pr.outputs.skip_ci != 'true' }}"472        env:473          NO_COLOR: true474          HOME: '${{ runner.temp }}/qwen-ci-home'475          USERPROFILE: '${{ runner.temp }}/qwen-ci-home'476          OPENAI_API_KEY: ''477          DASHSCOPE_API_KEY: ''478          QWEN_API_KEY: ''479          GEMINI_API_KEY: ''480          QWEN_DEFAULT_AUTH_TYPE: ''481        run: |-482          node -e "const fs = require('node:fs'); for (const key of ['HOME', 'USERPROFILE']) { const dir = process.env[key]; if (dir) fs.mkdirSync(dir, { recursive: true }); }"483          npm run test:ci484 485  post_coverage_comment:486    name: 'Post Coverage Comment'487    runs-on: 'ubuntu-latest'488    needs:489      - 'classify_pr'490      - 'test'491    # !cancelled() not always(): don't let a cancelled run hold the concurrency slot here.492    if: |-493      ${{494        !cancelled() &&495        needs.classify_pr.outputs.skip_ci != 'true' &&496        needs.test.outputs.ci_profile == 'full' &&497        github.event_name == 'pull_request' &&498        github.event.pull_request.head.repo.full_name == github.repository499      }}500    continue-on-error: true501    permissions:502      contents: 'read' # For checkout503      pull-requests: 'write' # For commenting504    strategy:505      matrix:506        # Reduce noise by only posting the comment once507        os:508          - 'ubuntu-latest'509        node-version:510          - '22.x'511    steps:512      - name: 'Checkout'513        uses: 'actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10'  # v6.0.3514 515      - name: 'Download coverage reports artifact'516        uses: 'actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c' # v8.0.1517        with:518          name: 'coverage-reports-${{ matrix.node-version }}-${{ matrix.os }}'519          path: 'coverage_artifact' # Download to a specific directory520 521      - name: 'Post Coverage Comment using Composite Action'522        uses: './.github/actions/post-coverage-comment' # Path to the composite action directory523        with:524          cli_json_file: 'coverage_artifact/cli/coverage/coverage-summary.json'525          core_json_file: 'coverage_artifact/core/coverage/coverage-summary.json'526          cli_full_text_summary_file: 'coverage_artifact/cli/coverage/full-text-summary.txt'527          core_full_text_summary_file: 'coverage_artifact/core/coverage/full-text-summary.txt'528          node_version: '${{ matrix.node-version }}'529          os: '${{ matrix.os }}'530          github_token: '${{ secrets.GITHUB_TOKEN }}'531 532  # Integration tests run only in the merge queue, not on every PR push.533  # They are the suite that previously ran *only* in the nightly Release534  # pipeline (`release.yml`), so regressions stayed hidden until release535  # time. Gating them on `merge_group` catches the failure before the PR536  # lands on `main`, while keeping the per-PR critical path fast. The537  # `merge_group` event runs in the base-repo context, so the same model538  # secrets used by the release jobs are available here.539  #540  # Until merge queue is enabled on `main` this job simply never triggers,541  # so adding it is a no-op for existing PR/push runs. Reuses the exact542  # `test:integration:cli:sandbox:none` script from `release.yml`.543  integration_cli:544    name: 'Integration Tests (CLI, No Sandbox)'545    needs: 'classify_pr'546    # Same ECS routing as the Ubuntu gate (via classify_pr): the merge queue runs547    # in the base-repo context, so use the self-hosted ECS pool and keep the548    # scarce hosted Linux runners free. Falls back to hosted if classify_pr is549    # skipped or the ECS kill-switch is set.550    if: "${{ !cancelled() && github.event_name == 'merge_group' }}"551    runs-on: '${{ fromJSON(needs.classify_pr.outputs.ubuntu_runner || ''["ubuntu-latest"]'') }}'552    permissions:553      contents: 'read'554    env:555      OPENAI_API_KEY: '${{ secrets.OPENAI_API_KEY }}'556      OPENAI_BASE_URL: '${{ secrets.OPENAI_BASE_URL }}'557      OPENAI_MODEL: '${{ secrets.OPENAI_MODEL }}'558    steps:559      - name: 'Checkout'560        uses: 'actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10'  # v6.0.3561        with:562          ref: "${{ github.event.inputs.branch_ref || (github.event_name == 'merge_group' && github.event.merge_group.head_sha) || github.ref }}"563          # Shallow, mirroring the Ubuntu gate: nothing here walks git history,564          # and a full-history clone is the heaviest transfer on the ECS runner.565          fetch-depth: 1566 567      # Same stale-checkout guard as the Ubuntu gate: this job now runs on ECS568      # via classify_pr, so fail loud if the checkout lacks the merge-queue head569      # rather than silently testing the wrong tree into a merge.570      - name: 'Verify checkout includes expected head commit'571        env:572          EXPECTED_SHA: '${{ github.event.merge_group.head_sha }}'573        run: |-574          if ! git merge-base --is-ancestor "${EXPECTED_SHA}" HEAD; then575            echo "::error::Checked out ref does not contain expected head ${EXPECTED_SHA}."576            git log --oneline --decorate -5577            exit 1578          fi579 580      # Hosted downloads Node; self-hosted ECS reuses its pre-installed Node 22581      # (it can't reach nodejs.org reliably). Mirrors the Ubuntu gate.582      - name: 'Setup Node.js (hosted)'583        if: "${{ runner.environment == 'github-hosted' }}"584        uses: 'actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e' # v6.4.0585        with:586          node-version-file: '.nvmrc'587          cache: 'npm'588          cache-dependency-path: 'package-lock.json'589 590      - name: 'Use pre-installed Node.js (self-hosted)'591        if: "${{ runner.environment == 'self-hosted' }}"592        run: |-593          if ! command -v node >/dev/null 2>&1; then594            echo "::error::Node.js is not on PATH for this self-hosted runner. Provision Node 22.x or set the MAINTAINER_ECS_RUNNER_DISABLED repository variable to 'true' to route the merge queue back to hosted runners."595            exit 1596          fi597          echo "Using pre-installed Node $(node -v) / npm $(npm -v)"598          if [[ "$(node -p 'process.versions.node.split(".")[0]')" != "22" ]]; then599            echo "::warning::Expected Node 22.x but found $(node -v); integration tests will run against the runner's Node."600          fi601 602      - name: 'Configure persistent npm cache (self-hosted)'603        if: "${{ runner.environment == 'self-hosted' }}"604        run: |-605          cache_dir="${HOME}/.cache/qwen-code/npm"606          mkdir -p "${cache_dir}"607          echo "NPM_CONFIG_CACHE=${cache_dir}" >> "${GITHUB_ENV}"608          echo "Using persistent npm cache at ${cache_dir}"609          du -sh "${cache_dir}" 2>/dev/null || true610 611      - name: 'Install Dependencies'612        env:613          NPM_CONFIG_PREFER_OFFLINE: 'true'614        run: |-615          npm ci --no-audit --progress=false616 617      - name: 'Report npm cache usage (self-hosted)'618        if: "${{ always() && runner.environment == 'self-hosted' }}"619        run: |-620          cache_dir="${NPM_CONFIG_CACHE:-$(npm config get cache)}"621          echo "npm cache: ${cache_dir}"622          du -sh "${cache_dir}" 2>/dev/null || true623 624      - name: 'Run CLI Integration Tests'625        run: |-626          npm run test:integration:cli:sandbox:none627 
basant307/AI_Governance_Project · CoolFace