mirror of
https://github.com/NanmiCoder/claude-code-haha.git
synced 2026-10-10 03:43:11 +08:00
ci: harden deterministic PR quality gates
Route required checks by changed surface, add offline provider and chat contracts, and keep fork PRs independent of live credentials. Layer agent guidance by subtree and enforce a compact instruction budget. Tested: bun run check:policy Confidence: high Scope-risk: broad
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
# CI and Repository Policy
|
||||
|
||||
These rules apply to `.github/` changes in addition to the root instructions.
|
||||
|
||||
- Treat workflow changes as product changes. Run `bun run check:policy`; run `actionlint` when available.
|
||||
- `scripts/pr/change-policy.ts` is the source of truth for path-to-check routing. Do not duplicate the routing graph in advisory automation.
|
||||
- Keep `pr-quality-gate` as the stable required status. Selected jobs must succeed and unselected jobs must be explicitly skipped; never convert failures into success.
|
||||
- Required PR jobs must remain offline and must not receive provider credentials or depend on paid/live services.
|
||||
- A `pull_request_target` workflow may inspect PR metadata using trusted base code, but must never execute the PR head, install PR-controlled dependencies, or expose secrets to contributor code.
|
||||
- Keep Bun aligned with `package.json#packageManager` and use frozen lockfile installs in required jobs.
|
||||
- Do not weaken coverage, test routing, CODEOWNERS, or branch protections to make another change pass. Maintainer override labels require an explicit, documented decision.
|
||||
- Repository settings, rulesets, secrets, and required-check changes require explicit user authorization; editing workflow files alone does not grant it.
|
||||
@@ -0,0 +1,40 @@
|
||||
# Quality policy and required-check topology are maintainer-owned.
|
||||
/.github/workflows/ @NanmiCoder
|
||||
/.github/CODEOWNERS @NanmiCoder
|
||||
/.github/copilot-instructions.md @NanmiCoder
|
||||
/.github/pull_request_template.md @NanmiCoder
|
||||
/AGENTS.md @NanmiCoder
|
||||
**/AGENTS.md @NanmiCoder
|
||||
/CONTRIBUTING.md @NanmiCoder
|
||||
/docs/en/guide/contributing.md @NanmiCoder
|
||||
/docs/guide/contributing.md @NanmiCoder
|
||||
/scripts/pr/ @NanmiCoder
|
||||
/scripts/quality-gate/ @NanmiCoder
|
||||
|
||||
# Cross-process and provider boundaries carry a wider blast radius.
|
||||
/desktop/electron/ @NanmiCoder
|
||||
/desktop/scripts/ @NanmiCoder
|
||||
/desktop/src/api/providers* @NanmiCoder
|
||||
/desktop/src/api/websocket* @NanmiCoder
|
||||
/desktop/src/lib/providerSettingsJson* @NanmiCoder
|
||||
/desktop/src/pages/ActiveSession* @NanmiCoder
|
||||
/desktop/src/pages/EmptySession* @NanmiCoder
|
||||
/desktop/src/stores/chatStore* @NanmiCoder
|
||||
/desktop/src/stores/providerStore* @NanmiCoder
|
||||
/desktop/src/stores/sessionRuntimeStore* @NanmiCoder
|
||||
/desktop/src/lib/persistenceMigrations* @NanmiCoder
|
||||
/src/server/api/providers* @NanmiCoder
|
||||
/src/server/config/provider* @NanmiCoder
|
||||
/src/server/proxy/ @NanmiCoder
|
||||
/src/server/services/conversationService* @NanmiCoder
|
||||
/src/server/services/persistentStorageMigrations* @NanmiCoder
|
||||
/src/server/services/openaiOfficialProvider* @NanmiCoder
|
||||
/src/server/services/provider* @NanmiCoder
|
||||
/src/server/types/provider* @NanmiCoder
|
||||
/src/server/ws/ @NanmiCoder
|
||||
/src/services/api/client* @NanmiCoder
|
||||
/src/services/compact/autoCompact* @NanmiCoder
|
||||
/src/services/openaiAuth/ @NanmiCoder
|
||||
/src/utils/managedEnv* @NanmiCoder
|
||||
/src/utils/model/ @NanmiCoder
|
||||
/src/utils/providerManagedEnvCompat* @NanmiCoder
|
||||
@@ -1,13 +1,18 @@
|
||||
# AI Coding Instructions
|
||||
|
||||
Follow the repository contract in `AGENTS.md` before editing code.
|
||||
Follow the root `AGENTS.md` and the nearest nested `AGENTS.md` for the files you edit.
|
||||
|
||||
For every feature or bugfix:
|
||||
For every feature, bugfix, refactor, or workflow change:
|
||||
|
||||
- Identify the changed surface before coding: `desktop`, `server`, `adapter`, `native`, `docs`, `provider/runtime`, `agent-loop`, or `release`.
|
||||
- Treat tool access as capability, not authorization. Do not commit, push, open/merge a PR, release, run live providers, or change repository settings unless explicitly requested.
|
||||
- Inspect `git status --short`, identify the changed surface, define the intended behavior and failure signal, and inspect the nearest implementation and tests before editing.
|
||||
- Identify the changed surface before coding: `desktop`, `server/runtime`, `adapter`, `native`, `docs`, `provider/runtime`, `agent-loop`, `persistence`, `policy/ci`, or `release`.
|
||||
- Add same-area tests with the production change. Do not leave production behavior untested unless the PR explicitly carries the maintainer override `allow-missing-tests`.
|
||||
- Preserve or improve the coverage ratchet. New or changed executable production lines must pass the changed-line coverage threshold in `scripts/quality-gate/coverage-thresholds.json`; do not edit coverage baselines or thresholds without maintainer approval via `allow-coverage-baseline-change`.
|
||||
- Use unit tests for pure logic, API/request-shape tests for server/provider/runtime behavior, Testing Library/Vitest for desktop UI and stores, and E2E or agent-browser smoke for user-visible cross-boundary flows.
|
||||
- For agent loop, tool execution, provider routing, model selection, file editing, permissions, session resume, and desktop chat changes, include mock/fixture tests and provide live smoke or baseline evidence when provider access is available.
|
||||
- Before marking work complete, run the narrow relevant check and then `bun run verify`; for high-risk Coding Agent paths, also run `bun run quality:providers` plus the appropriate `quality:smoke`, `quality:baseline`, or `quality:release` command.
|
||||
- In the final handoff or PR description, include changed files, tests added, coverage report path, E2E/live report path or blocker, and known residual risk.
|
||||
- Provider/auth/runtime-env/model-window/proxy changes require offline `bun run check:provider-contract`; desktop chat/WebSocket/session-runtime changes require `bun run check:chat-contract`.
|
||||
- Required PR evidence must be deterministic: use fake credentials, temporary config/home paths, mocked or loopback transports, explicit cleanup, and restored environment state. Never call a real provider or use saved machine credentials in required tests.
|
||||
- For agent loop, tool execution, provider routing, model selection, file editing, permissions, session resume, and desktop chat changes, include mock/fixture/contract tests. Live smoke is trusted-maintainer evidence only and requires explicit authorization; finding local credentials is not authorization.
|
||||
- Run the focused regression first, then `bun run check:impact` and every selected surface/contract check. Run `bun run verify` only before claiming PR-ready/push-ready or when full validation was requested.
|
||||
- Do not present skipped, blocked, not-run, mock, build-only, or stale evidence as passed live/runtime verification.
|
||||
- In the final handoff or PR description, include changed files, tests added, commands actually run with pass/fail counts, checks not run, coverage report path when generated, deterministic E2E evidence, live report path or explicit maintainer-only deferral, and known residual risk.
|
||||
|
||||
@@ -17,17 +17,15 @@
|
||||
|
||||
- [ ] I ran the relevant local checks, or explained why they do not apply.
|
||||
- [ ] I added or updated same-area tests for every production behavior change.
|
||||
- [ ] I ran `bun run verify` for code changes, including the coverage gate.
|
||||
- [ ] I ran the checks selected by `bun run check:impact`; if I claim PR-ready/full validation, I also ran `bun run verify`.
|
||||
- [ ] New or changed executable production lines meet the changed-line coverage threshold, or the blocker/maintainer override is documented.
|
||||
- [ ] I attached or summarized the quality report path, JUnit/log artifact path, and pass/fail/skip counts.
|
||||
- [ ] I ran E2E/live smoke for cross-boundary, provider/runtime, desktop chat, agent-loop, native, or release changes, or documented the blocker.
|
||||
- [ ] I ran deterministic E2E/contract checks for cross-boundary changes. Live-model evidence is attached when a trusted maintainer ran it, otherwise it is explicitly marked not run.
|
||||
|
||||
## Risk
|
||||
|
||||
- [ ] This PR does not touch CLI core paths, or it has maintainer approval for `allow-cli-core-change`.
|
||||
- [ ] Production code changes include matching tests, or have maintainer approval for `allow-missing-tests`.
|
||||
- [ ] Coverage baseline/threshold changes have maintainer approval for `allow-coverage-baseline-change`.
|
||||
- [ ] Quarantined tests still have owners, exit criteria, and unexpired review windows.
|
||||
- [ ] Provider/runtime changes were covered by mock contract tests, and live smoke was run or explicitly deferred.
|
||||
|
||||
@dosubot review this PR for changed-area risk, missing tests, docs impact, desktop startup risk, and CLI core impact.
|
||||
- [ ] Any quarantine-policy change has maintainer approval; deterministic provider/chat contract tests were not quarantined.
|
||||
- [ ] Provider/runtime changes are covered by offline mock/fixture contract tests; live smoke was run by a trusted maintainer or explicitly deferred.
|
||||
|
||||
+164
-106
@@ -13,17 +13,22 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
change-policy:
|
||||
name: change-policy
|
||||
scope-plan:
|
||||
name: scope-plan
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
areas: ${{ steps.policy.outputs.areas }}
|
||||
area_labels: ${{ steps.policy.outputs.area_labels }}
|
||||
blocked: ${{ steps.policy.outputs.blocked }}
|
||||
blocking_reasons: ${{ steps.policy.outputs.blocking_reasons }}
|
||||
desktop_checks: ${{ steps.policy.outputs.desktop_checks }}
|
||||
server_checks: ${{ steps.policy.outputs.server_checks }}
|
||||
adapter_checks: ${{ steps.policy.outputs.adapter_checks }}
|
||||
desktop_native_checks: ${{ steps.policy.outputs.desktop_native_checks }}
|
||||
provider_contract_checks: ${{ steps.policy.outputs.provider_contract_checks }}
|
||||
chat_contract_checks: ${{ steps.policy.outputs.chat_contract_checks }}
|
||||
persistence_checks: ${{ steps.policy.outputs.persistence_checks }}
|
||||
policy_checks: ${{ steps.policy.outputs.policy_checks }}
|
||||
docs_checks: ${{ steps.policy.outputs.docs_checks }}
|
||||
coverage_checks: ${{ steps.policy.outputs.coverage_checks }}
|
||||
steps:
|
||||
@@ -35,10 +40,7 @@ jobs:
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
|
||||
- name: Run policy tests
|
||||
run: bun run check:policy
|
||||
bun-version: 1.3.12
|
||||
|
||||
- name: Collect changed files
|
||||
env:
|
||||
@@ -49,86 +51,134 @@ jobs:
|
||||
gh api "repos/${REPOSITORY}/pulls/${PR_NUMBER}/files" --paginate --jq '.[].filename' > changed-files.txt
|
||||
jq -r '.pull_request.labels[].name' "$GITHUB_EVENT_PATH" > labels.txt
|
||||
|
||||
- name: Evaluate change policy
|
||||
- name: Build deterministic scope plan
|
||||
id: policy
|
||||
run: bun run scripts/pr/change-policy.ts --files changed-files.txt --labels-file labels.txt
|
||||
run: bun run scripts/pr/change-policy.ts --files changed-files.txt --labels-file labels.txt --plan-only
|
||||
|
||||
policy-enforcement:
|
||||
name: policy-enforcement
|
||||
needs: scope-plan
|
||||
if: ${{ always() && needs.scope-plan.result == 'success' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout policy sources
|
||||
if: needs.scope-plan.outputs.policy_checks == 'true'
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Bun
|
||||
if: needs.scope-plan.outputs.policy_checks == 'true'
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: 1.3.12
|
||||
|
||||
- name: Install root dependencies
|
||||
if: needs.scope-plan.outputs.policy_checks == 'true'
|
||||
run: bun install --frozen-lockfile
|
||||
|
||||
- name: Run policy regression tests
|
||||
if: needs.scope-plan.outputs.policy_checks == 'true'
|
||||
run: bun run check:policy
|
||||
|
||||
- name: Enforce change policy
|
||||
env:
|
||||
BLOCKED: ${{ needs.scope-plan.outputs.blocked }}
|
||||
BLOCKING_REASONS: ${{ needs.scope-plan.outputs.blocking_reasons }}
|
||||
run: |
|
||||
if [ "$BLOCKED" = "true" ]; then
|
||||
echo "::error::${BLOCKING_REASONS:-Change policy blocked this pull request}"
|
||||
exit 1
|
||||
fi
|
||||
echo "Change policy passed"
|
||||
|
||||
desktop-checks:
|
||||
name: desktop-checks
|
||||
needs: change-policy
|
||||
if: needs.change-policy.outputs.desktop_checks == 'true'
|
||||
needs: scope-plan
|
||||
if: needs.scope-plan.outputs.desktop_checks == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v4
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
|
||||
bun-version: 1.3.12
|
||||
- name: Install root dependencies
|
||||
run: bun install
|
||||
|
||||
run: bun install --frozen-lockfile
|
||||
- name: Install desktop dependencies
|
||||
working-directory: desktop
|
||||
run: bun install
|
||||
|
||||
run: bun install --frozen-lockfile
|
||||
- name: Run desktop checks
|
||||
run: bun run check:desktop
|
||||
|
||||
server-checks:
|
||||
name: server-checks
|
||||
needs: change-policy
|
||||
if: needs.change-policy.outputs.server_checks == 'true'
|
||||
needs: scope-plan
|
||||
if: needs.scope-plan.outputs.server_checks == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
- uses: actions/checkout@v4
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
|
||||
bun-version: 1.3.12
|
||||
- name: Install root dependencies
|
||||
run: bun install
|
||||
|
||||
- name: Run server checks
|
||||
run: bun install --frozen-lockfile
|
||||
- name: Run root runtime checks
|
||||
run: bun run check:server
|
||||
|
||||
provider-contract-checks:
|
||||
name: provider-contract-checks
|
||||
needs: scope-plan
|
||||
if: needs.scope-plan.outputs.provider_contract_checks == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: 1.3.12
|
||||
- name: Install root dependencies
|
||||
run: bun install --frozen-lockfile
|
||||
- name: Run offline provider contracts
|
||||
run: bun run check:provider-contract
|
||||
|
||||
chat-contract-checks:
|
||||
name: chat-contract-checks
|
||||
needs: scope-plan
|
||||
if: needs.scope-plan.outputs.chat_contract_checks == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: 1.3.12
|
||||
- name: Install root dependencies
|
||||
run: bun install --frozen-lockfile
|
||||
- name: Install desktop dependencies
|
||||
working-directory: desktop
|
||||
run: bun install --frozen-lockfile
|
||||
- name: Run desktop-server chat contracts
|
||||
run: bun run check:chat-contract
|
||||
|
||||
adapter-checks:
|
||||
name: adapter-checks
|
||||
needs: change-policy
|
||||
if: needs.change-policy.outputs.adapter_checks == 'true'
|
||||
needs: scope-plan
|
||||
if: needs.scope-plan.outputs.adapter_checks == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
- uses: actions/checkout@v4
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
|
||||
bun-version: 1.3.12
|
||||
- name: Install adapter dependencies
|
||||
working-directory: adapters
|
||||
run: bun install
|
||||
|
||||
run: bun install --frozen-lockfile
|
||||
- name: Run adapter checks
|
||||
run: bun run check:adapters
|
||||
|
||||
desktop-native-checks:
|
||||
name: desktop-native-checks
|
||||
needs: change-policy
|
||||
if: needs.change-policy.outputs.desktop_native_checks == 'true'
|
||||
needs: scope-plan
|
||||
if: needs.scope-plan.outputs.desktop_native_checks == 'true'
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Linux dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
@@ -136,72 +186,73 @@ jobs:
|
||||
build-essential curl wget file \
|
||||
libxdo-dev libssl-dev patchelf \
|
||||
libfuse2
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
|
||||
bun-version: 1.3.12
|
||||
- name: Install root dependencies
|
||||
run: bun install
|
||||
|
||||
run: bun install --frozen-lockfile
|
||||
- name: Install desktop dependencies
|
||||
working-directory: desktop
|
||||
run: bun install
|
||||
|
||||
run: bun install --frozen-lockfile
|
||||
- name: Run desktop native checks
|
||||
run: bun run check:native
|
||||
|
||||
docs-checks:
|
||||
name: docs-checks
|
||||
needs: change-policy
|
||||
if: needs.change-policy.outputs.docs_checks == 'true'
|
||||
persistence-checks:
|
||||
name: persistence-checks
|
||||
needs: scope-plan
|
||||
if: needs.scope-plan.outputs.persistence_checks == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v4
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: 1.3.12
|
||||
- name: Install root dependencies
|
||||
run: bun install --frozen-lockfile
|
||||
- name: Install desktop dependencies
|
||||
working-directory: desktop
|
||||
run: bun install --frozen-lockfile
|
||||
- name: Run persistence upgrade contracts
|
||||
run: bun run check:persistence-upgrade
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
docs-checks:
|
||||
name: docs-checks
|
||||
needs: scope-plan
|
||||
if: needs.scope-plan.outputs.docs_checks == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
|
||||
- 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'
|
||||
needs: scope-plan
|
||||
if: needs.scope-plan.outputs.coverage_checks == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
|
||||
bun-version: 1.3.12
|
||||
- name: Install root dependencies
|
||||
run: bun install
|
||||
|
||||
run: bun install --frozen-lockfile
|
||||
- name: Install desktop dependencies
|
||||
working-directory: desktop
|
||||
run: bun install
|
||||
|
||||
run: bun install --frozen-lockfile
|
||||
- name: Install adapter dependencies
|
||||
working-directory: adapters
|
||||
run: bun install
|
||||
|
||||
run: bun install --frozen-lockfile
|
||||
- name: Run coverage checks
|
||||
env:
|
||||
COVERAGE_BASE_REF: origin/${{ github.base_ref }}
|
||||
run: bun run check:coverage
|
||||
|
||||
- name: Summarize coverage report
|
||||
if: always()
|
||||
run: |
|
||||
@@ -209,7 +260,6 @@ jobs:
|
||||
if [ -n "$latest_report" ]; then
|
||||
cat "$latest_report" >> "$GITHUB_STEP_SUMMARY"
|
||||
fi
|
||||
|
||||
- name: Upload coverage report
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
@@ -221,17 +271,21 @@ jobs:
|
||||
pr-quality-gate:
|
||||
name: pr-quality-gate
|
||||
needs:
|
||||
- change-policy
|
||||
- scope-plan
|
||||
- policy-enforcement
|
||||
- desktop-checks
|
||||
- server-checks
|
||||
- provider-contract-checks
|
||||
- chat-contract-checks
|
||||
- adapter-checks
|
||||
- desktop-native-checks
|
||||
- persistence-checks
|
||||
- docs-checks
|
||||
- coverage-checks
|
||||
if: always()
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Require all selected quality jobs to pass
|
||||
- name: Require every selected quality job to pass
|
||||
run: |
|
||||
failures=0
|
||||
|
||||
@@ -246,27 +300,31 @@ jobs:
|
||||
fi
|
||||
}
|
||||
|
||||
allow_skip_or_success() {
|
||||
require_selected() {
|
||||
local name="$1"
|
||||
local result="$2"
|
||||
case "$result" in
|
||||
success|skipped)
|
||||
echo "$name: $result"
|
||||
;;
|
||||
*)
|
||||
echo "::error::$name ended with result: $result"
|
||||
failures=$((failures + 1))
|
||||
;;
|
||||
esac
|
||||
local expected="$2"
|
||||
local result="$3"
|
||||
if [ "$expected" = "true" ]; then
|
||||
require_success "$name" "$result"
|
||||
elif [ "$result" != "skipped" ]; then
|
||||
echo "::error::$name was not selected but ended with result: $result"
|
||||
failures=$((failures + 1))
|
||||
else
|
||||
echo "$name: skipped by scope plan"
|
||||
fi
|
||||
}
|
||||
|
||||
require_success "change-policy" "${{ needs.change-policy.result }}"
|
||||
allow_skip_or_success "desktop-checks" "${{ needs.desktop-checks.result }}"
|
||||
allow_skip_or_success "server-checks" "${{ needs.server-checks.result }}"
|
||||
allow_skip_or_success "adapter-checks" "${{ needs.adapter-checks.result }}"
|
||||
allow_skip_or_success "desktop-native-checks" "${{ needs.desktop-native-checks.result }}"
|
||||
allow_skip_or_success "docs-checks" "${{ needs.docs-checks.result }}"
|
||||
allow_skip_or_success "coverage-checks" "${{ needs.coverage-checks.result }}"
|
||||
require_success "scope-plan" "${{ needs.scope-plan.result }}"
|
||||
require_success "policy-enforcement" "${{ needs.policy-enforcement.result }}"
|
||||
require_selected "desktop-checks" "${{ needs.scope-plan.outputs.desktop_checks }}" "${{ needs.desktop-checks.result }}"
|
||||
require_selected "server-checks" "${{ needs.scope-plan.outputs.server_checks }}" "${{ needs.server-checks.result }}"
|
||||
require_selected "provider-contract-checks" "${{ needs.scope-plan.outputs.provider_contract_checks }}" "${{ needs.provider-contract-checks.result }}"
|
||||
require_selected "chat-contract-checks" "${{ needs.scope-plan.outputs.chat_contract_checks }}" "${{ needs.chat-contract-checks.result }}"
|
||||
require_selected "adapter-checks" "${{ needs.scope-plan.outputs.adapter_checks }}" "${{ needs.adapter-checks.result }}"
|
||||
require_selected "desktop-native-checks" "${{ needs.scope-plan.outputs.desktop_native_checks }}" "${{ needs.desktop-native-checks.result }}"
|
||||
require_selected "persistence-checks" "${{ needs.scope-plan.outputs.persistence_checks }}" "${{ needs.persistence-checks.result }}"
|
||||
require_selected "docs-checks" "${{ needs.scope-plan.outputs.docs_checks }}" "${{ needs.docs-checks.result }}"
|
||||
require_selected "coverage-checks" "${{ needs.scope-plan.outputs.coverage_checks }}" "${{ needs.coverage-checks.result }}"
|
||||
|
||||
if [ "$failures" -gt 0 ]; then
|
||||
exit 1
|
||||
|
||||
@@ -97,6 +97,7 @@ jobs:
|
||||
)
|
||||
const changedProduct = (predicate) => filenames.filter((file) =>
|
||||
predicate(file) &&
|
||||
/\.[cm]?[jt]sx?$/.test(file) &&
|
||||
!/\.test\.[cm]?[jt]sx?$/.test(file) &&
|
||||
!file.includes('/__tests__/') &&
|
||||
!file.includes('/fixtures/')
|
||||
@@ -105,11 +106,15 @@ jobs:
|
||||
const serverProduct = changedProduct((file) => file.startsWith('src/server/'))
|
||||
const adapterProduct = changedProduct((file) => file.startsWith('adapters/'))
|
||||
const agentRuntimeProduct = changedProduct((file) =>
|
||||
file.startsWith('src/server/ws/') ||
|
||||
file.startsWith('src/server/services/conversation') ||
|
||||
file.startsWith('src/tools/') ||
|
||||
file.startsWith('src/utils/')
|
||||
)
|
||||
const rootRuntimeProduct = changedProduct((file) =>
|
||||
file.startsWith('src/') &&
|
||||
!file.startsWith('src/server/') &&
|
||||
!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.')
|
||||
@@ -126,6 +131,15 @@ jobs:
|
||||
)) {
|
||||
missingTestSignals.push('Agent/runtime product files changed without a tools/utils test file in the PR.')
|
||||
}
|
||||
if (rootRuntimeProduct.length > 0 && !filenames.some((file) =>
|
||||
file.startsWith('src/') &&
|
||||
!file.startsWith('src/server/') &&
|
||||
!file.startsWith('src/tools/') &&
|
||||
!file.startsWith('src/utils/') &&
|
||||
(/\.test\.[cm]?[jt]sx?$/.test(file) || file.includes('/__tests__/'))
|
||||
)) {
|
||||
missingTestSignals.push('Root runtime product files changed without a matching root runtime test file in the PR.')
|
||||
}
|
||||
if (missingTestSignals.length > 0 && !currentLabels.has('allow-missing-tests')) {
|
||||
areas.add('needs-maintainer-approval')
|
||||
}
|
||||
@@ -137,31 +151,6 @@ jobs:
|
||||
areas.add('needs-maintainer-approval')
|
||||
}
|
||||
|
||||
const requiredChecks = ['change-policy']
|
||||
if (areas.has('area:desktop') || areas.has('area:server')) requiredChecks.push('desktop-checks')
|
||||
if (areas.has('area:server') || filenames.some((file) => file.startsWith('src/tools/') || file.startsWith('src/utils/'))) requiredChecks.push('server-checks')
|
||||
if (areas.has('area:adapters')) requiredChecks.push('adapter-checks')
|
||||
if (
|
||||
filenames.some((file) =>
|
||||
file.startsWith('desktop/') ||
|
||||
file.startsWith('adapters/') ||
|
||||
file.startsWith('src/server/') ||
|
||||
['bun.lock', 'package.json', 'desktop/bun.lock', 'desktop/package.json', 'desktop/package-lock.json', 'desktop/electron/tsconfig.json', 'desktop/scripts/build-linux.sh', 'desktop/scripts/build-macos-arm64.sh', 'desktop/scripts/build-windows-x64.ps1'].includes(file)
|
||||
)
|
||||
) 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 = []
|
||||
testSignals.push(...missingTestSignals.map((signal) => `BLOCKING unless \`allow-missing-tests\` is applied: ${signal}`))
|
||||
if (agentRuntimeProduct.length > 0) {
|
||||
@@ -266,8 +255,8 @@ jobs:
|
||||
'**Coverage policy files:**',
|
||||
coveragePolicyFilesText,
|
||||
'',
|
||||
'**Expected checks:**',
|
||||
requiredChecks.map((check) => `- \`${check}\``).join('\n'),
|
||||
'**Required check plan:**',
|
||||
'- See `PR Quality / scope-plan`; it is the single source of truth for selected jobs.',
|
||||
'',
|
||||
'**Test coverage signals:**',
|
||||
testSignals.map((signal) => `- ${signal}`).join('\n'),
|
||||
@@ -275,11 +264,7 @@ jobs:
|
||||
'**Risk notes:**',
|
||||
riskNotes.map((note) => `- ${note}`).join('\n'),
|
||||
'',
|
||||
'Hard merge gates still come from GitHub Actions, not AI review.',
|
||||
'',
|
||||
'**Dosu handoff:** Dosu can be used as the AI reviewer for risk explanation, missing-test prompts, and maintainer Q&A. If it does not comment automatically from the PR template, ask:',
|
||||
'',
|
||||
'@dosubot review this PR for changed-area risk, missing tests, docs impact, desktop startup risk, and CLI core impact.',
|
||||
'Hard merge gates come from the deterministic GitHub Actions contract lanes above.',
|
||||
].join('\n')
|
||||
|
||||
const comments = await github.paginate(github.rest.issues.listComments, {
|
||||
|
||||
Reference in New Issue
Block a user