diff --git a/.github/workflows/drift-detection.yml b/.github/workflows/drift-detection.yml index 5c1c52e..55a9922 100644 --- a/.github/workflows/drift-detection.yml +++ b/.github/workflows/drift-detection.yml @@ -10,7 +10,7 @@ jobs: name: Check for API Drift runs-on: ubuntu-latest outputs: - drift_detected: ${{ steps.drift.outputs.exit_code == '1' }} + drift_detected: ${{ steps.drift.outputs.has_drift }} steps: - uses: actions/checkout@v6 @@ -24,37 +24,61 @@ jobs: - name: Run drift detection id: drift - run: | - set +e - OUTPUT=$(npm run --silent detect-drift -- --json 2>/tmp/drift-stderr.log) - EXIT_CODE=$? - echo "exit_code=$EXIT_CODE" >> $GITHUB_OUTPUT - echo "$OUTPUT" > drift-report.json - cat drift-report.json - if [ -s /tmp/drift-stderr.log ]; then - echo "--- stderr ---" - cat /tmp/drift-stderr.log - fi - exit 0 - - - name: Parse drift report - if: steps.drift.outputs.exit_code == '1' - id: parse - run: | - REPORT=$(cat drift-report.json) - TOTAL=$(echo "$REPORT" | jq -r '.totalDrift') - SKILL_MISSING=$(echo "$REPORT" | jq -r '.drift.inSpecNotSkill | length') - SKILL_EXTRA=$(echo "$REPORT" | jq -r '.drift.inSkillNotSpec | length') - SDK_MISSING=$(echo "$REPORT" | jq -r '.drift.inSpecNotSdk | length') - SDK_EXTRA=$(echo "$REPORT" | jq -r '.drift.inSdkNotSpec | length') - echo "total=$TOTAL" >> $GITHUB_OUTPUT - echo "skill_missing=$SKILL_MISSING" >> $GITHUB_OUTPUT - echo "skill_extra=$SKILL_EXTRA" >> $GITHUB_OUTPUT - echo "sdk_missing=$SDK_MISSING" >> $GITHUB_OUTPUT - echo "sdk_extra=$SDK_EXTRA" >> $GITHUB_OUTPUT + uses: actions/github-script@v7 + with: + script: | + const { execSync } = require('child_process'); + + let stdout = ''; + let exitCode = 0; + + try { + stdout = execSync('npm run --silent detect-drift -- --json', { + encoding: 'utf-8', + stdio: ['pipe', 'pipe', 'pipe'], + timeout: 120_000, + }); + } catch (err) { + exitCode = err.status ?? 1; + stdout = err.stdout ?? ''; + const stderr = err.stderr ?? ''; + if (stderr) { + core.info(`--- stderr ---\n${stderr}`); + } + } + + core.setOutput('exit_code', String(exitCode)); + core.info(stdout); + + if (exitCode === 0) { + core.setOutput('has_drift', 'false'); + return; + } + + let report; + try { + report = JSON.parse(stdout); + } catch { + core.warning('Drift report is missing or malformed'); + core.setOutput('has_drift', 'false'); + return; + } + + if (!report.hasDrift) { + core.notice(`No actual drift found (totalDrift=${report.totalDrift}), skipping alert`); + core.setOutput('has_drift', 'false'); + return; + } + + core.setOutput('has_drift', 'true'); + core.setOutput('total', String(report.totalDrift)); + core.setOutput('skill_missing', String(report.drift.inSpecNotSkill.length)); + core.setOutput('skill_extra', String(report.drift.inSkillNotSpec.length)); + core.setOutput('sdk_missing', String(report.drift.inSpecNotSdk.length)); + core.setOutput('sdk_extra', String(report.drift.inSdkNotSpec.length)); - name: Alert on drift - if: steps.drift.outputs.exit_code == '1' + if: steps.drift.outputs.has_drift == 'true' uses: ./.github/actions/slack-post env: SLACK_TOKEN: ${{ secrets.SLACK_NOTIF_BOT_TOKEN }} @@ -63,10 +87,20 @@ jobs: header: 'SDK API Drift Detected' content: | [ - {"type": "section", "text": {"type": "mrkdwn", "text": "*${{ steps.parse.outputs.total }} endpoint(s) out of sync*\n\n*skill.md vs spec:*\n• Missing from skill.md: ${{ steps.parse.outputs.skill_missing }}\n• Extra in skill.md: ${{ steps.parse.outputs.skill_extra }}\n\n*SDK vs spec:*\n• Missing from SDK: ${{ steps.parse.outputs.sdk_missing }}\n• Extra in SDK: ${{ steps.parse.outputs.sdk_extra }}"}}, + {"type": "section", "text": {"type": "mrkdwn", "text": "*${{ steps.drift.outputs.total }} endpoint(s) out of sync*\n\n*skill.md vs spec:*\n• Missing from skill.md: ${{ steps.drift.outputs.skill_missing }}\n• Extra in skill.md: ${{ steps.drift.outputs.skill_extra }}\n\n*SDK vs spec:*\n• Missing from SDK: ${{ steps.drift.outputs.sdk_missing }}\n• Extra in SDK: ${{ steps.drift.outputs.sdk_extra }}"}}, {"type": "section", "text": {"type": "mrkdwn", "text": "<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View full report>"}} ] + - name: Alert on script error + if: steps.drift.outputs.exit_code != '0' && steps.drift.outputs.has_drift != 'true' + uses: ./.github/actions/slack-post + env: + SLACK_TOKEN: ${{ secrets.SLACK_NOTIF_BOT_TOKEN }} + with: + channel: '#agentic-service-alerts' + header: 'Drift Detection Script Error' + content: '[{"type": "section", "text": {"type": "mrkdwn", "text": "The drift detection script exited with code ${{ steps.drift.outputs.exit_code || ''unknown'' }} but produced no valid drift report. <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View run>"}}]' + - name: Notify on failure if: failure() uses: ./.github/actions/slack-post diff --git a/.github/workflows/regenerate-schemas.yml b/.github/workflows/regenerate-schemas.yml index cded268..2256304 100644 --- a/.github/workflows/regenerate-schemas.yml +++ b/.github/workflows/regenerate-schemas.yml @@ -13,6 +13,8 @@ jobs: name: Regenerate Schemas and Open PR runs-on: ubuntu-latest steps: + - uses: actions/checkout@v6 + - name: Generate GitHub App token id: app-token uses: actions/create-github-app-token@v3 @@ -20,9 +22,8 @@ jobs: client-id: ${{ vars.SCOPE3_WIZARD_CLIENT_ID }} private-key: ${{ secrets.SCOPE3_WIZARD_APP_PRIVATE_KEY }} - - uses: actions/checkout@v6 - with: - token: ${{ steps.app-token.outputs.token }} + - name: Configure git for app token + run: git remote set-url origin "https://x-access-token:${{ steps.app-token.outputs.token }}@github.com/${{ github.repository }}" - uses: actions/setup-node@v6 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fe7a033..36b19de 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,6 +15,11 @@ jobs: contents: read id-token: write steps: + - name: Checkout code + uses: actions/checkout@v6 + with: + fetch-depth: 0 + - name: Generate GitHub App token id: app-token uses: actions/create-github-app-token@v3 @@ -22,11 +27,8 @@ jobs: client-id: ${{ vars.SCOPE3_WIZARD_CLIENT_ID }} private-key: ${{ secrets.SCOPE3_WIZARD_APP_PRIVATE_KEY }} - - name: Checkout code - uses: actions/checkout@v6 - with: - fetch-depth: 0 - token: ${{ steps.app-token.outputs.token }} + - name: Configure git for app token + run: git remote set-url origin "https://x-access-token:${{ steps.app-token.outputs.token }}@github.com/${{ github.repository }}" # setup-node used directly: internal action doesn't support registry-url - name: Setup Node.js @@ -35,9 +37,6 @@ jobs: node-version: '22' registry-url: 'https://registry.npmjs.org' - - name: Update npm - run: npm install -g npm@latest - - name: Install dependencies run: npm ci diff --git a/package-lock.json b/package-lock.json index 4999337..8d186ea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -103,6 +103,7 @@ "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -167,6 +168,7 @@ "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", @@ -2460,6 +2462,7 @@ "integrity": "sha512-hRnu+5qggKDSyWHlnmThnUqg62l29Aj/6vcYgUaSFL9oc7DVjeWEQN3PRgdSc6F8d9QRMWkf36CLMch1Do/+RQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -2584,6 +2587,7 @@ "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "6.21.0", "@typescript-eslint/types": "6.21.0", @@ -2777,6 +2781,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2943,6 +2948,7 @@ "integrity": "sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "follow-redirects": "^1.15.11", "form-data": "^4.0.5", @@ -3187,6 +3193,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.9", "caniuse-lite": "^1.0.30001746", @@ -4006,6 +4013,7 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -4287,6 +4295,7 @@ "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", "license": "MIT", + "peer": true, "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -5317,6 +5326,7 @@ "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", @@ -6882,7 +6892,8 @@ "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/openapi-zod-client": { "version": "1.18.3", @@ -8430,6 +8441,7 @@ "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -8544,6 +8556,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -8874,6 +8887,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" }