mirror of
https://github.com/NanmiCoder/claude-code-haha.git
synced 2026-10-10 20:03:13 +08:00
feat: make quality gates observable and enforceable
The repository now has a measurable PR quality path instead of a loose set of manual checks. Coverage, quarantine governance, provider smoke, desktop smoke, and workflow wiring all produce durable reports that contributors and maintainers can inspect without reconstructing terminal output. This also fixes the desktop smoke current-runtime path so browser-driven smoke runs use the desktop default active provider instead of forcing the official current model, and records that runtime decision as an artifact. Constraint: Default PR gates must remain non-live and contributor-safe while live model checks stay explicit. Constraint: Release packaging is still GitHub Actions based, so release preflight must run before the build matrix. Rejected: Make live provider or desktop smoke mandatory on every PR | secrets, quotas, and model availability are maintainer-controlled. Rejected: Let PRs lower coverage baselines in the same change | base-branch ratchet comparison must remain authoritative. Confidence: high Scope-risk: moderate Directive: Do not relax coverage or quarantine policy without a maintainer approval label and a fresh quality report. Tested: ALLOW_CLI_CORE_CHANGE=1 ALLOW_COVERAGE_BASELINE_CHANGE=1 bun run quality:gate --mode pr Tested: bun run quality:gate --mode baseline --allow-live --only provider-smoke:* --provider-model nvidia-custom:main:nvidia-custom-main --artifacts-dir /tmp/quality-gate-live-smoke Tested: bun run quality:gate --mode baseline --allow-live --only desktop-smoke:* --provider-model current:current:current-runtime --artifacts-dir /tmp/quality-gate-desktop-smoke-fixed Tested: git diff --check Not-tested: Full live release mode with multiple providers in hosted CI; provider credentials and quota remain maintainer-controlled.
This commit is contained in:
@@ -25,9 +25,12 @@ jobs:
|
||||
adapter_checks: ${{ steps.policy.outputs.adapter_checks }}
|
||||
desktop_native_checks: ${{ steps.policy.outputs.desktop_native_checks }}
|
||||
docs_checks: ${{ steps.policy.outputs.docs_checks }}
|
||||
coverage_checks: ${{ steps.policy.outputs.coverage_checks }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
@@ -58,6 +61,8 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
@@ -179,3 +184,51 @@ jobs:
|
||||
|
||||
- name: Run docs checks
|
||||
run: npm ci && npm run docs:build
|
||||
|
||||
coverage-checks:
|
||||
name: coverage-checks
|
||||
needs: change-policy
|
||||
if: needs.change-policy.outputs.coverage_checks == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
|
||||
- name: Install root dependencies
|
||||
run: bun install
|
||||
|
||||
- name: Install desktop dependencies
|
||||
working-directory: desktop
|
||||
run: bun install
|
||||
|
||||
- name: Install adapter dependencies
|
||||
working-directory: adapters
|
||||
run: bun install
|
||||
|
||||
- name: Run coverage checks
|
||||
env:
|
||||
COVERAGE_BASE_REF: origin/${{ github.base_ref }}
|
||||
run: bun run check:coverage
|
||||
|
||||
- name: Summarize coverage report
|
||||
if: always()
|
||||
run: |
|
||||
latest_report="$(find artifacts/coverage -name coverage-report.md -print | sort | tail -n 1)"
|
||||
if [ -n "$latest_report" ]; then
|
||||
cat "$latest_report" >> "$GITHUB_STEP_SUMMARY"
|
||||
fi
|
||||
|
||||
- name: Upload coverage report
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: coverage-report
|
||||
path: artifacts/coverage/
|
||||
retention-days: 14
|
||||
|
||||
@@ -35,6 +35,8 @@ jobs:
|
||||
'area:cli-core': 'b60205',
|
||||
'needs-maintainer-approval': 'b60205',
|
||||
'allow-cli-core-change': 'c2e0c6',
|
||||
'allow-missing-tests': 'c2e0c6',
|
||||
'allow-coverage-baseline-change': 'c2e0c6',
|
||||
}
|
||||
|
||||
const files = await github.paginate(github.rest.pulls.listFiles, {
|
||||
@@ -107,6 +109,32 @@ jobs:
|
||||
file.startsWith('src/tools/') ||
|
||||
file.startsWith('src/utils/')
|
||||
)
|
||||
const missingTestSignals = []
|
||||
if (desktopProduct.length > 0 && !hasTest('desktop/src/')) {
|
||||
missingTestSignals.push('Desktop product files changed without a desktop test file in the PR.')
|
||||
}
|
||||
if (serverProduct.length > 0 && !hasTest('src/server/')) {
|
||||
missingTestSignals.push('Server product files changed without a server test file in the PR.')
|
||||
}
|
||||
if (adapterProduct.length > 0 && !hasTest('adapters/')) {
|
||||
missingTestSignals.push('Adapter product files changed without an adapter test file in the PR.')
|
||||
}
|
||||
if (agentRuntimeProduct.length > 0 && !filenames.some((file) =>
|
||||
(file.startsWith('src/tools/') || file.startsWith('src/utils/')) &&
|
||||
(/\.test\.[cm]?[jt]sx?$/.test(file) || file.includes('/__tests__/'))
|
||||
)) {
|
||||
missingTestSignals.push('Agent/runtime product files changed without a tools/utils test file in the PR.')
|
||||
}
|
||||
if (missingTestSignals.length > 0 && !currentLabels.has('allow-missing-tests')) {
|
||||
areas.add('needs-maintainer-approval')
|
||||
}
|
||||
const coveragePolicyFiles = filenames.filter((file) =>
|
||||
file === 'scripts/quality-gate/coverage-baseline.json' ||
|
||||
file === 'scripts/quality-gate/coverage-thresholds.json'
|
||||
)
|
||||
if (coveragePolicyFiles.length > 0 && !currentLabels.has('allow-coverage-baseline-change')) {
|
||||
areas.add('needs-maintainer-approval')
|
||||
}
|
||||
|
||||
const requiredChecks = ['change-policy']
|
||||
if (areas.has('area:desktop') || areas.has('area:server')) requiredChecks.push('desktop-checks')
|
||||
@@ -121,17 +149,20 @@ jobs:
|
||||
)
|
||||
) requiredChecks.push('desktop-native-checks')
|
||||
if (areas.has('area:docs')) requiredChecks.push('docs-checks')
|
||||
if (
|
||||
filenames.some((file) =>
|
||||
file.startsWith('desktop/src/') ||
|
||||
file.startsWith('src/server/') ||
|
||||
file.startsWith('src/tools/') ||
|
||||
file.startsWith('src/utils/') ||
|
||||
file.startsWith('adapters/') ||
|
||||
file.startsWith('scripts/quality-gate/') ||
|
||||
['package.json', 'desktop/package.json', 'desktop/bun.lock'].includes(file)
|
||||
)
|
||||
) requiredChecks.push('coverage-checks')
|
||||
|
||||
const testSignals = []
|
||||
if (desktopProduct.length > 0 && !hasTest('desktop/src/')) {
|
||||
testSignals.push('Desktop product files changed without a desktop test file in the PR.')
|
||||
}
|
||||
if (serverProduct.length > 0 && !hasTest('src/server/')) {
|
||||
testSignals.push('Server product files changed without a server test file in the PR.')
|
||||
}
|
||||
if (adapterProduct.length > 0 && !hasTest('adapters/')) {
|
||||
testSignals.push('Adapter product files changed without an adapter test file in the PR.')
|
||||
}
|
||||
testSignals.push(...missingTestSignals.map((signal) => `BLOCKING unless \`allow-missing-tests\` is applied: ${signal}`))
|
||||
if (agentRuntimeProduct.length > 0) {
|
||||
testSignals.push('Agent/model runtime path changed: use mock/request-shape tests in PR and maintainer live-model smoke before release.')
|
||||
}
|
||||
@@ -183,7 +214,7 @@ jobs:
|
||||
}
|
||||
|
||||
for (const label of Object.keys(managedLabels)) {
|
||||
if (label === 'allow-cli-core-change') continue
|
||||
if (label === 'allow-cli-core-change' || label === 'allow-missing-tests' || label === 'allow-coverage-baseline-change') continue
|
||||
if (!areas.has(label) && currentLabels.has(label)) {
|
||||
try {
|
||||
await github.rest.issues.removeLabel({
|
||||
@@ -203,9 +234,18 @@ jobs:
|
||||
const approvalText = cliCoreFiles.length > 0 && !currentLabels.has('allow-cli-core-change')
|
||||
? 'Blocked by policy until a maintainer applies `allow-cli-core-change` and approves the PR.'
|
||||
: 'No CLI-core policy block detected.'
|
||||
const missingTestText = missingTestSignals.length > 0 && !currentLabels.has('allow-missing-tests')
|
||||
? 'Blocked by policy until a maintainer applies `allow-missing-tests` or matching tests are added.'
|
||||
: 'No missing-test policy block detected.'
|
||||
const coveragePolicyText = coveragePolicyFiles.length > 0 && !currentLabels.has('allow-coverage-baseline-change')
|
||||
? 'Blocked by policy until a maintainer applies `allow-coverage-baseline-change` after reviewing the baseline or threshold change.'
|
||||
: 'No coverage-baseline policy block detected.'
|
||||
const cliFilesText = cliCoreFiles.length
|
||||
? cliCoreFiles.slice(0, 20).map((file) => `- \`${file}\``).join('\n')
|
||||
: '- none'
|
||||
const coveragePolicyFilesText = coveragePolicyFiles.length
|
||||
? coveragePolicyFiles.slice(0, 20).map((file) => `- \`${file}\``).join('\n')
|
||||
: '- none'
|
||||
|
||||
const body = [
|
||||
marker,
|
||||
@@ -215,9 +255,16 @@ jobs:
|
||||
'',
|
||||
`**CLI core policy:** ${approvalText}`,
|
||||
'',
|
||||
`**Missing-test policy:** ${missingTestText}`,
|
||||
'',
|
||||
`**Coverage baseline policy:** ${coveragePolicyText}`,
|
||||
'',
|
||||
'**CLI core files:**',
|
||||
cliFilesText,
|
||||
'',
|
||||
'**Coverage policy files:**',
|
||||
coveragePolicyFilesText,
|
||||
'',
|
||||
'**Expected checks:**',
|
||||
requiredChecks.map((check) => `- \`${check}\``).join('\n'),
|
||||
'',
|
||||
|
||||
@@ -19,7 +19,73 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
quality-preflight:
|
||||
name: Quality preflight
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install Linux dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y \
|
||||
build-essential curl wget file \
|
||||
libxdo-dev libssl-dev \
|
||||
libwebkit2gtk-4.1-dev \
|
||||
libayatana-appindicator3-dev \
|
||||
librsvg2-dev patchelf \
|
||||
libfuse2
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Install root dependencies
|
||||
run: bun install
|
||||
|
||||
- name: Install desktop dependencies
|
||||
working-directory: desktop
|
||||
run: bun install
|
||||
|
||||
- name: Install adapter dependencies
|
||||
working-directory: adapters
|
||||
run: bun install
|
||||
|
||||
- name: Run release quality preflight
|
||||
run: bun run quality:gate --mode pr --artifacts-dir artifacts/quality-runs
|
||||
|
||||
- name: Summarize quality report
|
||||
if: always()
|
||||
shell: bash
|
||||
run: |
|
||||
latest_report="$(find artifacts/quality-runs -name report.md -print | sort | tail -n 1)"
|
||||
if [ -n "$latest_report" ]; then
|
||||
cat "$latest_report" >> "$GITHUB_STEP_SUMMARY"
|
||||
fi
|
||||
|
||||
- name: Upload quality report
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: release-quality-gate
|
||||
path: artifacts/quality-runs/
|
||||
retention-days: 14
|
||||
|
||||
build:
|
||||
needs: quality-preflight
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
|
||||
Reference in New Issue
Block a user