diff --git a/.github/workflows/frontmatter-validation.yml b/.github/workflows/frontmatter-validation.yml index 3501aff06..4ab36a18b 100644 --- a/.github/workflows/frontmatter-validation.yml +++ b/.github/workflows/frontmatter-validation.yml @@ -77,7 +77,10 @@ jobs: } & scripts/linting/Validate-MarkdownFrontmatter.ps1 @params - continue-on-error: true + if ($LASTEXITCODE -ne 0) { + "FRONTMATTER_VALIDATION_FAILED=true" >> $env:GITHUB_ENV + } + exit 0 - name: Upload frontmatter validation results if: always() diff --git a/.github/workflows/link-lang-check.yml b/.github/workflows/link-lang-check.yml index b1e851ec2..a105328db 100644 --- a/.github/workflows/link-lang-check.yml +++ b/.github/workflows/link-lang-check.yml @@ -33,7 +33,10 @@ jobs: shell: pwsh run: | & scripts/linting/Invoke-LinkLanguageCheck.ps1 -ExcludePaths 'scripts/tests/**' - continue-on-error: true + if ($LASTEXITCODE -ne 0) { + "LINK_LANG_FAILED=true" >> $env:GITHUB_ENV + } + exit 0 - name: Upload results if: always() diff --git a/.github/workflows/markdown-link-check.yml b/.github/workflows/markdown-link-check.yml index 97102594a..54aac295e 100644 --- a/.github/workflows/markdown-link-check.yml +++ b/.github/workflows/markdown-link-check.yml @@ -8,6 +8,10 @@ on: required: false type: boolean default: false + outputs: + has-warnings: + description: 'Whether broken links were detected (true when issues exist regardless of soft-fail)' + value: ${{ jobs.markdown-link-check.outputs.has-warnings }} permissions: contents: read @@ -18,6 +22,8 @@ jobs: runs-on: ubuntu-latest permissions: contents: read + outputs: + has-warnings: ${{ steps.link-check.outputs.has-warnings }} steps: - name: Checkout code uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 @@ -43,7 +49,11 @@ jobs: shell: pwsh run: | & scripts/linting/Markdown-Link-Check.ps1 - continue-on-error: ${{ inputs.soft-fail }} + if ($LASTEXITCODE -ne 0) { + "LINK_CHECK_FAILED=true" >> $env:GITHUB_ENV + "has-warnings=true" >> $env:GITHUB_OUTPUT + } + exit 0 - name: Upload markdown link check results if: always() @@ -54,7 +64,7 @@ jobs: retention-days: 30 - name: Check results and fail if needed - if: ${{ !inputs.soft-fail && steps.link-check.outcome == 'failure' }} + if: env.LINK_CHECK_FAILED == 'true' && !inputs.soft-fail shell: pwsh run: | Write-Host "Markdown link check failed and soft-fail is false. Failing the job." diff --git a/.github/workflows/markdown-lint.yml b/.github/workflows/markdown-lint.yml index 11acda5b0..b00ca4c02 100644 --- a/.github/workflows/markdown-lint.yml +++ b/.github/workflows/markdown-lint.yml @@ -19,6 +19,8 @@ jobs: runs-on: ubuntu-latest permissions: contents: read + env: + MARKDOWN_LINT_FAILED: 'false' steps: - name: Checkout code uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 @@ -40,9 +42,15 @@ jobs: - name: Run markdown lint id: markdown-lint run: | - npm run lint:md > markdown-lint-output.txt 2>&1 || echo "MARKDOWN_LINT_FAILED=true" >> "$GITHUB_ENV" + set +e + npm run lint:md > markdown-lint-output.txt 2>&1 + EXIT_CODE=$? + set -e + if [ "$EXIT_CODE" -ne 0 ]; then + echo "MARKDOWN_LINT_FAILED=true" >> "$GITHUB_ENV" + fi cat markdown-lint-output.txt - continue-on-error: true + exit 0 - name: Create annotations if: env.MARKDOWN_LINT_FAILED == 'true' @@ -60,7 +68,14 @@ jobs: - name: Add job summary if: always() run: | - if [ "${{ env.MARKDOWN_LINT_FAILED }}" == "true" ]; then + if [ "${{ env.MARKDOWN_LINT_FAILED }}" == "true" ] && [ "${{ inputs.soft-fail }}" == "true" ]; then + { + echo "## Markdown Lint Results" + echo "⚠️ **Status**: Warnings (soft-fail)" + echo "" + echo "Markdown linting violations detected but soft-fail is enabled. Review the artifact for details." + } >> "$GITHUB_STEP_SUMMARY" + elif [ "${{ env.MARKDOWN_LINT_FAILED }}" == "true" ]; then { echo "## Markdown Lint Results" echo "❌ **Status**: Failed" diff --git a/.github/workflows/pr-validation.yml b/.github/workflows/pr-validation.yml index 4e1a9c2a6..37fd180e5 100644 --- a/.github/workflows/pr-validation.yml +++ b/.github/workflows/pr-validation.yml @@ -341,3 +341,118 @@ jobs: security-events: write # Required for SARIF upload to Security tab actions: read + validation-gate: + name: Validation Gate + if: always() + needs: + - spell-check + - markdown-lint + - table-format + - psscriptanalyzer + - discover-python-projects + - python-lint + - copyright-headers + - yaml-lint + - pester-tests + - pytest + - fuzz-tests + - pip-audit + - docusaurus-tests + - frontmatter-validation + - adr-consistency-validation + - ai-artifact-validation + - msdate-freshness + - plugin-validation + - skill-validation + - link-lang-check + - markdown-link-check + - dependency-pinning-check + - workflow-permissions-check + - action-version-consistency-scan + - gitleaks-scan + - npm-audit + - codeql + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Check job results + shell: bash + run: | + { + echo "## Validation Gate Summary" + echo "" + } >> "$GITHUB_STEP_SUMMARY" + + FAILED="" + + check_result() { + local name="$1" + local result="$2" + local soft_fail="${3:-false}" + local has_warnings="${4:-}" + + if [ "$result" = "success" ] && [ "$soft_fail" = "true" ] && [ "$has_warnings" = "true" ]; then + echo "⚠️ **$name**: passed with warnings (soft-fail)" >> "$GITHUB_STEP_SUMMARY" + elif [ "$result" = "success" ]; then + echo "✅ **$name**: passed" >> "$GITHUB_STEP_SUMMARY" + elif [ "$result" = "skipped" ]; then + echo "⏭️ **$name**: skipped" >> "$GITHUB_STEP_SUMMARY" + elif [ "$result" = "failure" ] && [ "$soft_fail" = "true" ]; then + echo "⚠️ **$name**: failed (soft-fail, non-blocking)" >> "$GITHUB_STEP_SUMMARY" + elif [ "$result" = "failure" ]; then + echo "❌ **$name**: failed" >> "$GITHUB_STEP_SUMMARY" + FAILED="${FAILED}${name}, " + elif [ "$result" = "cancelled" ]; then + echo "⚠️ **$name**: cancelled" >> "$GITHUB_STEP_SUMMARY" + FAILED="${FAILED}${name} (cancelled), " + else + echo "⚠️ **$name**: ${result}" >> "$GITHUB_STEP_SUMMARY" + FAILED="${FAILED}${name} (${result}), " + fi + } + + check_result "Spell Check" "${{ needs.spell-check.result }}" + check_result "Markdown Lint" "${{ needs.markdown-lint.result }}" + check_result "Table Format" "${{ needs.table-format.result }}" + check_result "PowerShell Lint" "${{ needs.psscriptanalyzer.result }}" + check_result "Discover Python Projects" "${{ needs.discover-python-projects.result }}" + check_result "Python Lint" "${{ needs.python-lint.result }}" + check_result "Copyright Headers" "${{ needs.copyright-headers.result }}" + check_result "YAML Lint" "${{ needs.yaml-lint.result }}" + check_result "PowerShell Tests" "${{ needs.pester-tests.result }}" + check_result "Python Tests" "${{ needs.pytest.result }}" + check_result "Fuzz Tests" "${{ needs.fuzz-tests.result }}" + check_result "pip-audit" "${{ needs.pip-audit.result }}" + check_result "Docusaurus Tests" "${{ needs.docusaurus-tests.result }}" + check_result "Frontmatter Validation" "${{ needs.frontmatter-validation.result }}" + check_result "ADR Consistency" "${{ needs.adr-consistency-validation.result }}" + check_result "AI Artifact Validation" "${{ needs.ai-artifact-validation.result }}" + check_result "ms.date Freshness" "${{ needs.msdate-freshness.result }}" + check_result "Plugin Validation" "${{ needs.plugin-validation.result }}" + check_result "Skill Validation" "${{ needs.skill-validation.result }}" + check_result "Link Language Check" "${{ needs.link-lang-check.result }}" + check_result "Markdown Link Check" "${{ needs.markdown-link-check.result }}" "true" "${{ needs.markdown-link-check.outputs.has-warnings }}" + check_result "Dependency Pinning" "${{ needs.dependency-pinning-check.result }}" + check_result "Workflow Permissions" "${{ needs.workflow-permissions-check.result }}" + check_result "Action Version Consistency" "${{ needs.action-version-consistency-scan.result }}" + check_result "Gitleaks" "${{ needs.gitleaks-scan.result }}" + check_result "npm Audit" "${{ needs.npm-audit.result }}" + check_result "CodeQL" "${{ needs.codeql.result }}" + + if [ -n "$FAILED" ]; then + { + echo "" + echo "---" + echo "❌ **Validation failed**: ${FAILED%, }" + } >> "$GITHUB_STEP_SUMMARY" + echo "One or more required checks failed: ${FAILED%, }" + exit 1 + fi + + { + echo "" + echo "---" + echo "✅ **All validations passed**" + } >> "$GITHUB_STEP_SUMMARY" + diff --git a/.github/workflows/release-stable.yml b/.github/workflows/release-stable.yml index f7e37c37a..344beea00 100644 --- a/.github/workflows/release-stable.yml +++ b/.github/workflows/release-stable.yml @@ -159,21 +159,88 @@ jobs: matrix: directory: ${{ fromJson(needs.discover-python-projects.outputs.directories) }} - release-please: - name: Release Please + validation-gate: + name: Validation Gate + if: always() needs: - spell-check - markdown-lint - table-format - dependency-pinning-scan + - action-version-consistency-scan - gitleaks-scan - pester-tests - docusaurus-tests + - discover-python-projects - python-lint - pytest - # Allow release-please to run when conditional CI jobs (python-lint, - # pytest) are skipped. Block only on actual failures or cancellations. - if: ${{ !cancelled() && !failure() }} + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Check job results + shell: bash + run: | + { + echo "## Validation Gate Summary" + echo "" + } >> "$GITHUB_STEP_SUMMARY" + + FAILED="" + + check_result() { + local name="$1" + local result="$2" + + if [ "$result" = "success" ]; then + echo "✅ **$name**: passed" >> "$GITHUB_STEP_SUMMARY" + elif [ "$result" = "skipped" ]; then + echo "⏭️ **$name**: skipped" >> "$GITHUB_STEP_SUMMARY" + elif [ "$result" = "failure" ]; then + echo "❌ **$name**: failed" >> "$GITHUB_STEP_SUMMARY" + FAILED="${FAILED}${name}, " + elif [ "$result" = "cancelled" ]; then + echo "⚠️ **$name**: cancelled" >> "$GITHUB_STEP_SUMMARY" + FAILED="${FAILED}${name} (cancelled), " + else + echo "⚠️ **$name**: ${result}" >> "$GITHUB_STEP_SUMMARY" + FAILED="${FAILED}${name} (${result}), " + fi + } + + check_result "Spell Check" "${{ needs.spell-check.result }}" + check_result "Markdown Lint" "${{ needs.markdown-lint.result }}" + check_result "Table Format" "${{ needs.table-format.result }}" + check_result "Dependency Pinning Scan" "${{ needs.dependency-pinning-scan.result }}" + check_result "Action Version Consistency" "${{ needs.action-version-consistency-scan.result }}" + check_result "Gitleaks" "${{ needs.gitleaks-scan.result }}" + check_result "PowerShell Tests" "${{ needs.pester-tests.result }}" + check_result "Docusaurus Tests" "${{ needs.docusaurus-tests.result }}" + check_result "Discover Python Projects" "${{ needs.discover-python-projects.result }}" + check_result "Python Lint" "${{ needs.python-lint.result }}" + check_result "Python Tests" "${{ needs.pytest.result }}" + + if [ -n "$FAILED" ]; then + { + echo "" + echo "---" + echo "❌ **Validation failed**: ${FAILED%, }" + } >> "$GITHUB_STEP_SUMMARY" + echo "One or more required checks failed: ${FAILED%, }" + exit 1 + fi + + { + echo "" + echo "---" + echo "✅ **All validations passed**" + } >> "$GITHUB_STEP_SUMMARY" + + release-please: + name: Release Please + needs: + - validation-gate + if: ${{ success() }} runs-on: ubuntu-latest outputs: release_created: ${{ steps.release.outputs.release_created }} diff --git a/.github/workflows/spell-check.yml b/.github/workflows/spell-check.yml index 46edad8b7..143338a00 100644 --- a/.github/workflows/spell-check.yml +++ b/.github/workflows/spell-check.yml @@ -19,6 +19,8 @@ jobs: runs-on: ubuntu-latest permissions: contents: read + env: + SPELL_CHECK_FAILED: 'false' steps: - name: Checkout code uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 @@ -40,16 +42,15 @@ jobs: - name: Run spell check id: spell-check run: | - set +e # Disable errexit to capture exit code + set +e npm run spell-check > logs/spell-check-results.txt 2>&1 EXIT_CODE=$? - set -e # Re-enable errexit + set -e if [ "$EXIT_CODE" -ne 0 ]; then echo "SPELL_CHECK_FAILED=true" >> "$GITHUB_ENV" fi cat logs/spell-check-results.txt - exit "$EXIT_CODE" - continue-on-error: true + exit 0 - name: Create annotations if: env.SPELL_CHECK_FAILED == 'true' @@ -67,7 +68,14 @@ jobs: - name: Add job summary if: always() run: | - if [ "${{ env.SPELL_CHECK_FAILED }}" == "true" ]; then + if [ "${{ env.SPELL_CHECK_FAILED }}" == "true" ] && [ "${{ inputs.soft-fail }}" == "true" ]; then + { + echo "## Spell Check Results" + echo "⚠️ **Status**: Warnings (soft-fail)" + echo "" + echo "Spelling errors were detected but soft-fail is enabled. Review the artifact for details." + } >> "$GITHUB_STEP_SUMMARY" + elif [ "${{ env.SPELL_CHECK_FAILED }}" == "true" ]; then { echo "## Spell Check Results" echo "❌ **Status**: Failed" diff --git a/.github/workflows/table-format.yml b/.github/workflows/table-format.yml index 4f0a803cf..58b3a8f92 100644 --- a/.github/workflows/table-format.yml +++ b/.github/workflows/table-format.yml @@ -19,6 +19,8 @@ jobs: runs-on: ubuntu-latest permissions: contents: read + env: + TABLE_FORMAT_FAILED: 'false' steps: - name: Checkout code uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 @@ -49,7 +51,7 @@ jobs: git diff --name-only fi fi - continue-on-error: true + exit 0 - name: Create annotations if: env.TABLE_FORMAT_FAILED == 'true' @@ -69,7 +71,16 @@ jobs: run: | { echo "## Table Format Check Results" - if [ "${{ env.TABLE_FORMAT_FAILED }}" == "true" ]; then + if [ "${{ env.TABLE_FORMAT_FAILED }}" == "true" ] && [ "${{ inputs.soft-fail }}" == "true" ]; then + echo "⚠️ **Status**: Warnings (soft-fail)" + echo "" + echo "Table formatting issues found but soft-fail is enabled." + echo "" + echo "**To fix manually:**" + echo "1. Run \`npm run format:tables\` locally" + echo "2. Review and commit the changes" + echo "3. Push to update this PR" + elif [ "${{ env.TABLE_FORMAT_FAILED }}" == "true" ]; then echo "❌ **Status**: Failed" echo "" echo "⚠️ **Note**: This check does NOT auto-fix tables."