diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e03ca25af2ff..66210c97c8ef 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,14 +60,11 @@ jobs: plugin_contracts_matrix: ${{ steps.manifest.outputs.plugin_contracts_matrix }} channel_contracts_matrix: ${{ steps.manifest.outputs.channel_contracts_matrix }} run_checks: ${{ steps.manifest.outputs.run_checks }} - checks_matrix: ${{ steps.manifest.outputs.checks_matrix }} run_checks_node_core_nondist: ${{ steps.manifest.outputs.run_checks_node_core_nondist }} checks_node_core_nondist_matrix: ${{ steps.manifest.outputs.checks_node_core_nondist_matrix }} run_checks_node_core_dist: ${{ steps.manifest.outputs.run_checks_node_core_dist }} - checks_node_core_dist_matrix: ${{ steps.manifest.outputs.checks_node_core_dist_matrix }} run_check: ${{ steps.manifest.outputs.run_check }} run_check_additional: ${{ steps.manifest.outputs.run_check_additional }} - run_build_smoke: ${{ steps.manifest.outputs.run_build_smoke }} run_check_docs: ${{ steps.manifest.outputs.run_check_docs }} run_control_ui_i18n: ${{ steps.manifest.outputs.run_control_ui_i18n }} run_checks_windows: ${{ steps.manifest.outputs.run_checks_windows }} @@ -134,6 +131,7 @@ jobs: OPENCLAW_CI_RUN_CONTROL_UI_I18N: ${{ github.event_name == 'workflow_dispatch' && 'true' || steps.changed_scope.outputs.run_control_ui_i18n || 'false' }} OPENCLAW_CI_CHECKOUT_REVISION: ${{ steps.checkout_ref.outputs.sha }} OPENCLAW_CI_REPOSITORY: ${{ github.repository }} + OPENCLAW_CI_EVENT_NAME: ${{ github.event_name }} run: | node --input-type=module <<'EOF' import { appendFileSync } from "node:fs"; @@ -175,6 +173,7 @@ jobs: const isCanonicalRepository = process.env.OPENCLAW_CI_REPOSITORY === "openclaw/openclaw"; const docsOnly = parseBoolean(process.env.OPENCLAW_CI_DOCS_ONLY); const docsChanged = parseBoolean(process.env.OPENCLAW_CI_DOCS_CHANGED); + const eventName = process.env.OPENCLAW_CI_EVENT_NAME ?? ""; const runNode = parseBoolean(process.env.OPENCLAW_CI_RUN_NODE) && !docsOnly; const runNodeFastOnly = runNode && parseBoolean(process.env.OPENCLAW_CI_RUN_NODE_FAST_ONLY); @@ -199,7 +198,7 @@ jobs: const checksFastCoreTasks = []; if (runNodeFull) { checksFastCoreTasks.push( - { check_name: "checks-fast-bundled", runtime: "node", task: "bundled" }, + { check_name: "checks-fast-bundled-protocol", runtime: "node", task: "bundled-protocol" }, ); } else { if (runNodeFastCiRouting) { @@ -248,21 +247,12 @@ jobs: runNodeFull ? createChannelContractTestShards() : [], ), run_checks: runNodeFull, - checks_matrix: createMatrix( - runNodeFull - ? [ - { check_name: "checks-node-channels", runtime: "node", task: "channels" }, - ] - : [], - ), run_checks_node_core_nondist: nodeTestNonDistShards.length > 0, checks_node_core_nondist_matrix: createMatrix(nodeTestNonDistShards), run_checks_node_core_dist: nodeTestDistShards.length > 0, - checks_node_core_dist_matrix: createMatrix(nodeTestDistShards), run_check: runNodeFull, run_check_additional: runNodeFull, - run_build_smoke: runNodeFull, - run_check_docs: docsChanged, + run_check_docs: docsChanged && eventName !== "push", run_control_ui_i18n: runControlUiI18n, run_skills_python_job: runSkillsPython, run_checks_windows: runWindows, @@ -297,9 +287,9 @@ jobs: } EOF - # Run the fast security/SCM checks in parallel with scope detection so the + # Run dependency-free security checks in parallel with scope detection so the # main Node jobs do not have to wait for Python/pre-commit setup. - security-scm-fast: + security-fast: permissions: contents: read if: github.event_name != 'pull_request' || !github.event.pull_request.draft @@ -392,22 +382,6 @@ jobs: printf 'Auditing workflow files:\n%s\n' "${workflow_files[@]}" pre-commit run --config "${PRE_COMMIT_CONFIG_PATH:-.pre-commit-config.yaml}" zizmor --files "${workflow_files[@]}" - security-dependency-audit: - permissions: - contents: read - if: github.event_name != 'pull_request' || !github.event.pull_request.draft - runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04') }} - timeout-minutes: 10 - steps: - - name: Checkout - uses: actions/checkout@v6 - with: - ref: ${{ inputs.target_ref || github.sha }} - fetch-depth: 1 - fetch-tags: false - persist-credentials: false - submodules: false - - name: Setup Node.js uses: actions/setup-node@v6 with: @@ -417,35 +391,6 @@ jobs: - name: Audit production dependencies run: node scripts/pre-commit/pnpm-audit-prod.mjs --audit-level=high - security-fast: - permissions: {} - needs: [security-scm-fast, security-dependency-audit] - if: ${{ !cancelled() && always() && (github.event_name != 'pull_request' || !github.event.pull_request.draft) }} - runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04') }} - timeout-minutes: 5 - steps: - - name: Verify fast security jobs - env: - DEPENDENCY_AUDIT_RESULT: ${{ needs.security-dependency-audit.result }} - SCM_RESULT: ${{ needs.security-scm-fast.result }} - run: | - set -euo pipefail - failed=0 - - for result in \ - "security-scm-fast=${SCM_RESULT}" \ - "security-dependency-audit=${DEPENDENCY_AUDIT_RESULT}" - do - job="${result%%=*}" - status="${result#*=}" - if [ "$status" != "success" ]; then - echo "::error::${job} ended with ${status}" - failed=1 - fi - done - - exit "$failed" - # Build dist once for Node-relevant changes and share it with downstream jobs. # Keep this overlapping with the fast correctness lanes so green PRs get heavy # test/build feedback sooner instead of waiting behind a full `check` pass. @@ -733,14 +678,9 @@ jobs: run: | set -euo pipefail case "$TASK" in - bundled) + bundled-protocol) pnpm test:bundled - ;; - contracts-channels) - pnpm test:contracts:channels - ;; - contracts-plugins) - pnpm test:contracts:plugins + pnpm protocol:check ;; contracts-plugins-ci-routing) pnpm test:contracts:plugins @@ -923,71 +863,6 @@ jobs: EOF OPENCLAW_VITEST_INCLUDE_FILE="$include_file" pnpm test:contracts:channels - checks-fast-protocol: - permissions: - contents: read - name: "checks-fast-protocol" - needs: [preflight] - if: needs.preflight.outputs.run_checks_fast == 'true' - runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04') }} - timeout-minutes: 30 - steps: - - name: Checkout - shell: bash - env: - CHECKOUT_REPO: ${{ github.repository }} - CHECKOUT_SHA: ${{ needs.preflight.outputs.checkout_revision }} - CHECKOUT_TOKEN: ${{ github.token }} - run: | - set -euo pipefail - - workdir="$GITHUB_WORKSPACE" - auth_header="$(printf 'x-access-token:%s' "$CHECKOUT_TOKEN" | base64 | tr -d '\n')" - - reset_checkout_dir() { - mkdir -p "$workdir" - find "$workdir" -mindepth 1 -maxdepth 1 -exec rm -rf {} + - } - - checkout_attempt() { - local attempt="$1" - - reset_checkout_dir - git init "$workdir" >/dev/null - git config --global --add safe.directory "$workdir" - git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}" - git -C "$workdir" config gc.auto 0 - - timeout --signal=TERM 30s git -C "$workdir" \ - -c protocol.version=2 \ - -c "http.https://github.com/.extraheader=AUTHORIZATION: basic ${auth_header}" \ - fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \ - "+${CHECKOUT_SHA}:refs/remotes/origin/ci-target" || return 1 - - git -C "$workdir" checkout --force --detach "$CHECKOUT_SHA" || return 1 - test -f "$workdir/.github/actions/setup-node-env/action.yml" || return 1 - echo "checkout attempt ${attempt}/5 succeeded" - } - - for attempt in 1 2 3 4 5; do - if checkout_attempt "$attempt"; then - exit 0 - fi - echo "checkout attempt ${attempt}/5 failed" - sleep $((attempt * 5)) - done - - echo "checkout failed after 5 attempts" >&2 - exit 1 - - - name: Setup Node environment - uses: ./.github/actions/setup-node-env - with: - install-bun: "false" - - - name: Run protocol check - run: pnpm protocol:check - checks-node-compat: permissions: contents: read @@ -1188,8 +1063,8 @@ jobs: fail-fast: false matrix: include: - - check_name: check-preflight-guards - task: preflight-guards + - check_name: check-guards + task: guards runner: blacksmith-4vcpu-ubuntu-2404 - check_name: check-prod-types task: prod-types @@ -1200,15 +1075,9 @@ jobs: - check_name: check-dependencies task: dependencies runner: blacksmith-8vcpu-ubuntu-2404 - - check_name: check-policy-guards - task: policy-guards - runner: blacksmith-4vcpu-ubuntu-2404 - check_name: check-test-types task: test-types runner: blacksmith-4vcpu-ubuntu-2404 - - check_name: check-strict-smoke - task: strict-smoke - runner: blacksmith-4vcpu-ubuntu-2404 steps: - name: Checkout shell: bash @@ -1271,12 +1140,18 @@ jobs: run: | set -euo pipefail case "$TASK" in - preflight-guards) + guards) pnpm check:no-conflict-markers pnpm tool-display:check pnpm check:host-env-policy:swift pnpm dup:check:coverage pnpm deps:patches:check + pnpm lint:webhook:no-low-level-body-read + pnpm lint:auth:no-pairing-store-group + pnpm lint:auth:pairing-account-scope + pnpm check:import-cycles + # build-artifacts already runs the tsdown/runtime build for the same Node-relevant changes. + pnpm build:plugin-sdk:strict-smoke ;; prod-types) pnpm tsgo:prod @@ -1293,19 +1168,9 @@ jobs: pnpm deadcode:ci fi ;; - policy-guards) - pnpm lint:webhook:no-low-level-body-read - pnpm lint:auth:no-pairing-store-group - pnpm lint:auth:pairing-account-scope - pnpm check:import-cycles - ;; test-types) pnpm check:test-types ;; - strict-smoke) - # build-artifacts already runs the tsdown/runtime build for the same Node-relevant changes. - pnpm build:plugin-sdk:strict-smoke - ;; *) echo "Unsupported check task: $TASK" >&2 exit 1 @@ -1335,15 +1200,9 @@ jobs: - check_name: check-additional-boundaries-a group: boundaries boundary_shard: 1/4 - - check_name: check-additional-boundaries-b + - check_name: check-additional-boundaries-bcd group: boundaries - boundary_shard: 2/4 - - check_name: check-additional-boundaries-c - group: boundaries - boundary_shard: 3/4 - - check_name: check-additional-boundaries-d - group: boundaries - boundary_shard: 4/4 + boundary_shard: 2/4,3/4,4/4 - check_name: check-additional-extension-channels group: extension-channels - check_name: check-additional-extension-bundled diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index d2f3ecac409b..a7c731fc93b6 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -36,5 +36,15 @@ jobs: with: install-bun: "false" + - name: Checkout ClawHub docs source + uses: actions/checkout@v6 + with: + repository: openclaw/clawhub + path: clawhub-source + fetch-depth: 1 + persist-credentials: false + - name: Check docs + env: + OPENCLAW_DOCS_SYNC_CLAWHUB_REPO: ${{ github.workspace }}/clawhub-source run: pnpm check:docs diff --git a/docs/ci.md b/docs/ci.md index 1cda4fa0fe96..ab0bf36f52bb 100644 --- a/docs/ci.md +++ b/docs/ci.md @@ -15,13 +15,10 @@ OpenClaw CI runs on every push to `main` and every pull request. The `preflight` | Job | Purpose | When it runs | | ---------------------------------- | --------------------------------------------------------------------------------------------------------- | ---------------------------------- | | `preflight` | Detect docs-only changes, changed scopes, changed extensions, and build the CI manifest | Always on non-draft pushes and PRs | -| `security-scm-fast` | Private key detection and workflow audit via `zizmor` | Always on non-draft pushes and PRs | -| `security-dependency-audit` | Dependency-free production lockfile audit against npm advisories | Always on non-draft pushes and PRs | -| `security-fast` | Required aggregate for the fast security jobs | Always on non-draft pushes and PRs | +| `security-fast` | Private key detection, workflow audit via `zizmor`, and production lockfile audit | Always on non-draft pushes and PRs | | `check-dependencies` | Production Knip dependency-only pass plus the unused-file allowlist guard | Node-relevant changes | | `build-artifacts` | Build `dist/`, Control UI, built-CLI smoke checks, embedded built-artifact checks, and reusable artifacts | Node-relevant changes | -| `checks-fast-core` | Fast Linux correctness lanes such as bundled and CI-routing checks | Node-relevant changes | -| `checks-fast-protocol` | Gateway protocol compatibility check | Node-relevant changes | +| `checks-fast-core` | Fast Linux correctness lanes such as bundled, protocol, and CI-routing checks | Node-relevant changes | | `checks-fast-contracts-plugins-*` | Two sharded plugin contract checks | Node-relevant changes | | `checks-fast-contracts-channels-*` | Two sharded channel contract checks | Node-relevant changes | | `checks-node-core-*` | Core Node test shards, excluding channel, bundled, contract, and extension lanes | Node-relevant changes | @@ -40,7 +37,7 @@ OpenClaw CI runs on every push to `main` and every pull request. The `preflight` ## Fail-fast order 1. `preflight` decides which lanes exist at all. The `docs-scope` and `changed-scope` logic are steps inside this job, not standalone jobs. -2. `security-scm-fast`, `security-dependency-audit`, `security-fast`, `check-*`, `check-additional-*`, `check-docs`, and `skills-python` fail quickly without waiting on the heavier artifact and platform matrix jobs. +2. `security-fast`, `check-*`, `check-additional-*`, `check-docs`, and `skills-python` fail quickly without waiting on the heavier artifact and platform matrix jobs. 3. `build-artifacts` overlaps with the fast Linux lanes so downstream consumers can start as soon as the shared build is ready. 4. Heavier platform and runtime lanes fan out after that: `checks-fast-core`, `checks-fast-contracts-plugins-*`, `checks-fast-contracts-channels-*`, `checks-node-core-*`, `checks-windows`, `macos-node`, `macos-swift`, and `android`. @@ -53,10 +50,11 @@ The `ci-timings-summary` job uploads a compact `ci-timings-summary` artifact for Scope logic lives in `scripts/ci-changed-scope.mjs` and is covered by unit tests in `src/scripts/ci-changed-scope.test.ts`. Manual dispatch skips changed-scope detection and makes the preflight manifest act as if every scoped area changed. - **CI workflow edits** validate the Node CI graph plus workflow linting, but do not force Windows, Android, or macOS native builds by themselves; those platform lanes stay scoped to platform source changes. +- **Docs on `main` pushes** are checked by the standalone `Docs` workflow with the same ClawHub docs mirror used by CI, so mixed code+docs pushes do not also queue the CI `check-docs` shard. Pull requests and manual CI still run `check-docs` from CI when docs changed. - **CI routing-only edits, selected cheap core-test fixture edits, and narrow plugin contract helper/test-routing edits** use a fast Node-only manifest path: `preflight`, security, and a single `checks-fast-core` task. That path skips build artifacts, Node 22 compatibility, channel contracts, full core shards, bundled-plugin shards, and additional guard matrices when the change is limited to the routing or helper surfaces the fast task exercises directly. - **Windows Node checks** are scoped to Windows-specific process/path wrappers, npm/pnpm/UI runner helpers, package manager config, and the CI workflow surfaces that execute that lane; unrelated source, plugin, install-smoke, and test-only changes stay on the Linux Node lanes. -The slowest Node test families are split or balanced so each job stays small without over-reserving runners: plugin contracts and channel contracts each run as two weighted Blacksmith-backed shards with the standard GitHub runner fallback, core unit fast/support lanes run separately, core runtime infra is split between state, process/config, cron, and shared shards, auto-reply runs as balanced workers (with the reply subtree split into agent-runner, dispatch, and commands/state-routing shards), and agentic gateway/server configs are split across chat/auth/model/http-plugin/runtime/startup lanes instead of waiting on built artifacts. Broad browser, QA, media, and miscellaneous plugin tests use their dedicated Vitest configs instead of the shared plugin catch-all. Include-pattern shards record timing entries using the CI shard name, so `.artifacts/vitest-shard-timings.json` can distinguish a whole config from a filtered shard. `check-additional-*` keeps package-boundary compile/canary work together and separates runtime topology architecture from gateway watch coverage; the boundary guard list is striped across four matrix shards, each running selected independent guards concurrently and printing per-check timings. The expensive Codex happy-path prompt snapshot drift check runs as its own additional job for manual CI and for prompt-affecting changes only, so normal unrelated Node changes do not wait behind cold prompt snapshot generation and the boundary shards stay balanced while prompt drift is still pinned to the PR that caused it; the same flag skips prompt snapshot Vitest generation inside the built-artifact core support-boundary shard. Gateway watch, channel tests, and the core support-boundary shard run concurrently inside `build-artifacts` after `dist/` and `dist-runtime/` are already built. +The slowest Node test families are split or balanced so each job stays small without over-reserving runners: plugin contracts and channel contracts each run as two weighted Blacksmith-backed shards with the standard GitHub runner fallback, core unit fast/support lanes run separately, core runtime infra is split between state, process/config, cron, and shared shards, auto-reply runs as balanced workers (with the reply subtree split into agent-runner, dispatch, and commands/state-routing shards), and agentic gateway/server configs are split across chat/auth/model/http-plugin/runtime/startup lanes instead of waiting on built artifacts. Broad browser, QA, media, and miscellaneous plugin tests use their dedicated Vitest configs instead of the shared plugin catch-all. Include-pattern shards record timing entries using the CI shard name, so `.artifacts/vitest-shard-timings.json` can distinguish a whole config from a filtered shard. `check-additional-*` keeps package-boundary compile/canary work together and separates runtime topology architecture from gateway watch coverage; the boundary guard list is striped into one prompt-heavy shard and one combined shard for the remaining guard stripes, each running selected independent guards concurrently and printing per-check timings. The expensive Codex happy-path prompt snapshot drift check runs as its own additional job for manual CI and for prompt-affecting changes only, so normal unrelated Node changes do not wait behind cold prompt snapshot generation and the boundary shards stay balanced while prompt drift is still pinned to the PR that caused it; the same flag skips prompt snapshot Vitest generation inside the built-artifact core support-boundary shard. Gateway watch, channel tests, and the core support-boundary shard run concurrently inside `build-artifacts` after `dist/` and `dist-runtime/` are already built. Android CI runs both `testPlayDebugUnitTest` and `testThirdPartyDebugUnitTest` and then builds the Play debug APK. The third-party flavor has no separate source set or manifest; its unit-test lane still compiles the flavor with the SMS/call-log BuildConfig flags, while avoiding a duplicate debug APK packaging job on every Android-relevant push. @@ -93,15 +91,15 @@ gh workflow run full-release-validation.yml --ref main -f ref= ## Runners -| Runner | Jobs | -| -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ubuntu-24.04` | `preflight`, fast security jobs and aggregates (`security-scm-fast`, `security-dependency-audit`, `security-fast`), fast protocol/contract/bundled checks, docs checks, Python skills, workflow-sanity, labeler, auto-response; install-smoke preflight also uses GitHub-hosted Ubuntu so the Blacksmith matrix can queue earlier | -| `blacksmith-4vcpu-ubuntu-2404` | `CodeQL Critical Quality`, lower-weight extension shards, `checks-fast-core`, `checks-fast-protocol`, plugin/channel contract shards, `checks-node-compat-node22`, `check-prod-types`, and `check-test-types` | -| `blacksmith-8vcpu-ubuntu-2404` | Linux Node test shards, bundled plugin test shards, `check-additional-*` shards, `android` | -| `blacksmith-16vcpu-ubuntu-2404` | `build-artifacts`, `check-lint` (CPU-sensitive enough that 8 vCPU cost more than they saved); install-smoke Docker builds (32-vCPU queue time cost more than it saved) | -| `blacksmith-16vcpu-windows-2025` | `checks-windows` | -| `blacksmith-6vcpu-macos-latest` | `macos-node` on `openclaw/openclaw`; forks fall back to `macos-latest` | -| `blacksmith-12vcpu-macos-latest` | `macos-swift` on `openclaw/openclaw`; forks fall back to `macos-latest` | +| Runner | Jobs | +| -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `ubuntu-24.04` | `preflight`, docs checks, Python skills, workflow-sanity, labeler, auto-response; install-smoke preflight also uses GitHub-hosted Ubuntu so the Blacksmith matrix can queue earlier | +| `blacksmith-4vcpu-ubuntu-2404` | `CodeQL Critical Quality`, `security-fast`, lower-weight extension shards, `checks-fast-core`, plugin/channel contract shards, `checks-node-compat-node22`, `check-guards`, `check-prod-types`, and `check-test-types` | +| `blacksmith-8vcpu-ubuntu-2404` | Linux Node test shards, bundled plugin test shards, `check-additional-*` shards, `android` | +| `blacksmith-16vcpu-ubuntu-2404` | `build-artifacts`, `check-lint` (CPU-sensitive enough that 8 vCPU cost more than they saved); install-smoke Docker builds (32-vCPU queue time cost more than it saved) | +| `blacksmith-16vcpu-windows-2025` | `checks-windows` | +| `blacksmith-6vcpu-macos-latest` | `macos-node` on `openclaw/openclaw`; forks fall back to `macos-latest` | +| `blacksmith-12vcpu-macos-latest` | `macos-swift` on `openclaw/openclaw`; forks fall back to `macos-latest` | Canonical-repo CI keeps Blacksmith as the default runner path. During `preflight`, `scripts/ci-runner-labels.mjs` checks recent queued and in-progress Actions runs for queued Blacksmith jobs. If a specific Blacksmith label already has queued jobs, downstream jobs that would use that exact label fall back to the matching GitHub-hosted runner (`ubuntu-24.04`, `windows-2025`, or `macos-latest`) for that run only. Other Blacksmith sizes in the same OS family stay on their primary labels. If the API probe fails, no fallback is applied. diff --git a/scripts/lib/channel-contract-test-plan.mjs b/scripts/lib/channel-contract-test-plan.mjs index 770202a7dc93..66587610f4e9 100644 --- a/scripts/lib/channel-contract-test-plan.mjs +++ b/scripts/lib/channel-contract-test-plan.mjs @@ -30,19 +30,19 @@ const CONTRACT_FILE_WEIGHTS = new Map([ ["outbound-payload.contract.test.ts", 18], ["plugins-core.catalog.paths.contract.test.ts", 28], ["plugins-core.catalog.entries.contract.test.ts", 16], - ["session-binding.registry-backed.contract.test.ts", 16], + ["session-binding.registry-backed.contract.test.ts", 40], ]); function resolveContractFileWeight(file) { const name = file.replaceAll("\\", "/").split("/").pop(); if (name.startsWith("plugin.registry-backed-shard-")) { - return 40; + return 48; } if (name.startsWith("surfaces-only.registry-backed-shard-")) { return 40; } if (name.startsWith("directory.registry-backed-shard-")) { - return 24; + return 36; } if (name.startsWith("threading.registry-backed-shard-")) { return 18; diff --git a/scripts/run-additional-boundary-checks.mjs b/scripts/run-additional-boundary-checks.mjs index 603a2847f334..3486651e8789 100644 --- a/scripts/run-additional-boundary-checks.mjs +++ b/scripts/run-additional-boundary-checks.mjs @@ -92,12 +92,38 @@ export function parseShardSpec(value) { return { count, index: index - 1, label: `${index}/${count}` }; } +export function parseShardSelection(value) { + if (!value) { + return null; + } + return String(value) + .split(",") + .map((part) => part.trim()) + .filter(Boolean) + .map((part) => { + const shard = parseShardSpec(part); + if (!shard) { + throw new Error(`Invalid shard spec '${value}'`); + } + return shard; + }); +} + export function selectChecksForShard(checks, shardSpec) { - const shard = typeof shardSpec === "string" ? parseShardSpec(shardSpec) : shardSpec; - if (!shard) { + const shards = + typeof shardSpec === "string" + ? parseShardSelection(shardSpec) + : Array.isArray(shardSpec) + ? shardSpec + : shardSpec + ? [shardSpec] + : null; + if (!shards || shards.length === 0) { return checks; } - return checks.filter((_check, index) => index % shard.count === shard.index); + return checks.filter((_check, index) => + shards.some((shard) => index % shard.count === shard.index), + ); } export function formatCommand({ command, args }) { @@ -239,11 +265,11 @@ if (import.meta.url === `file://${process.argv[1]}`) { process.env.OPENCLAW_ADDITIONAL_BOUNDARY_CONCURRENCY ?? process.env.OPENCLAW_EXTENSION_BOUNDARY_CONCURRENCY, ); - const shard = parseShardSpec(resolveCliShardSpec(process.argv.slice(2), process.env)); - const checks = selectChecksForShard(BOUNDARY_CHECKS, shard); - if (shard) { + const shards = parseShardSelection(resolveCliShardSpec(process.argv.slice(2), process.env)); + const checks = selectChecksForShard(BOUNDARY_CHECKS, shards); + if (shards) { process.stdout.write( - `Running ${checks.length}/${BOUNDARY_CHECKS.length} additional boundary checks (shard ${shard.label})\n`, + `Running ${checks.length}/${BOUNDARY_CHECKS.length} additional boundary checks (shard ${shards.map((shard) => shard.label).join(",")})\n`, ); } const failures = await runChecks(checks, { concurrency }); diff --git a/test/scripts/ci-workflow-guards.test.ts b/test/scripts/ci-workflow-guards.test.ts index 5ecd886cf05a..0ec777dcdd49 100644 --- a/test/scripts/ci-workflow-guards.test.ts +++ b/test/scripts/ci-workflow-guards.test.ts @@ -5,10 +5,21 @@ describe("ci workflow guards", () => { it("runs the package patch guard in PR CI preflight", () => { const workflow = readFileSync(".github/workflows/ci.yml", "utf8"); const preflightGuards = workflow.slice( - workflow.indexOf("preflight-guards)"), + workflow.indexOf("guards)"), workflow.indexOf("prod-types)"), ); + expect(workflow).toContain("check-guards"); expect(preflightGuards).toContain("pnpm deps:patches:check"); }); + + it("keeps push docs validation ClawHub-backed", () => { + const workflow = readFileSync(".github/workflows/docs.yml", "utf8"); + + expect(workflow).toContain("repository: openclaw/clawhub"); + expect(workflow).toContain("path: clawhub-source"); + expect(workflow).toContain( + "OPENCLAW_DOCS_SYNC_CLAWHUB_REPO: ${{ github.workspace }}/clawhub-source", + ); + }); }); diff --git a/test/scripts/plugin-prerelease-test-plan.test.ts b/test/scripts/plugin-prerelease-test-plan.test.ts index 810cf5a4da62..62678b4a7024 100644 --- a/test/scripts/plugin-prerelease-test-plan.test.ts +++ b/test/scripts/plugin-prerelease-test-plan.test.ts @@ -291,6 +291,7 @@ describe("scripts/lib/plugin-prerelease-test-plan.mjs", () => { "${{ github.event_name == 'workflow_dispatch' && 'true' || steps.docs_scope.outputs.docs_changed }}", OPENCLAW_CI_DOCS_ONLY: "${{ github.event_name == 'workflow_dispatch' && 'false' || steps.docs_scope.outputs.docs_only }}", + OPENCLAW_CI_EVENT_NAME: "${{ github.event_name }}", OPENCLAW_CI_REPOSITORY: "${{ github.repository }}", OPENCLAW_CI_RUN_ANDROID: "${{ github.event_name == 'workflow_dispatch' && inputs.include_android && 'true' || steps.changed_scope.outputs.run_android || 'false' }}", diff --git a/test/scripts/run-additional-boundary-checks.test.ts b/test/scripts/run-additional-boundary-checks.test.ts index ea077a123675..c02257592bef 100644 --- a/test/scripts/run-additional-boundary-checks.test.ts +++ b/test/scripts/run-additional-boundary-checks.test.ts @@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest"; import { BOUNDARY_CHECKS, formatCommand, + parseShardSelection, parseShardSpec, resolveConcurrency, runChecks, @@ -44,9 +45,16 @@ describe("run-additional-boundary-checks", () => { it("parses and applies CI shard specs", () => { expect(parseShardSpec("2/4")).toEqual({ count: 4, index: 1, label: "2/4" }); + expect(parseShardSelection("2/4,3/4")).toEqual([ + { count: 4, index: 1, label: "2/4" }, + { count: 4, index: 2, label: "3/4" }, + ]); expect(selectChecksForShard(BOUNDARY_CHECKS, "1/4")).toEqual( BOUNDARY_CHECKS.filter((_check, index) => index % 4 === 0), ); + expect(selectChecksForShard(BOUNDARY_CHECKS, "2/4,3/4")).toEqual( + BOUNDARY_CHECKS.filter((_check, index) => index % 4 === 1 || index % 4 === 2), + ); const shardedLabels = [1, 2, 3, 4].flatMap((index) => selectChecksForShard(BOUNDARY_CHECKS, `${index}/4`).map((check) => check.label), );