diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4b94b56fba3..2490d0491e4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -66,6 +66,7 @@ jobs: 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 }} + additional_matrix: ${{ steps.manifest.outputs.additional_matrix }} 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 }} @@ -205,6 +206,44 @@ jobs: parseBoolean(process.env.OPENCLAW_CI_RUN_CONTROL_UI_I18N) && !docsOnly; const runPromptSnapshots = parseBoolean(process.env.OPENCLAW_CI_RUN_PROMPT_SNAPSHOTS) && !docsOnly; + const additionalCheckTasks = [ + { + check_name: "check-additional-boundaries-a", + group: "boundaries", + boundary_shard: "1/4", + }, + { + check_name: "check-additional-boundaries-b", + 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", + }, + { check_name: "check-additional-extension-channels", group: "extension-channels" }, + { check_name: "check-additional-extension-bundled", group: "extension-bundled" }, + { + check_name: "check-additional-extension-package-boundary", + group: "extension-package-boundary", + }, + { + check_name: "check-additional-runtime-topology-architecture", + group: "runtime-topology-architecture", + }, + ]; + if (runPromptSnapshots) { + additionalCheckTasks.push({ + check_name: "check-additional-prompt-snapshots", + group: "prompt-snapshots", + }); + } const checksFastCoreTasks = []; if (runNodeFull) { checksFastCoreTasks.push( @@ -270,6 +309,7 @@ jobs: checks_node_core_dist_matrix: createMatrix(nodeTestDistShards), run_check: runNodeFull, run_check_additional: runNodeFull, + additional_matrix: createMatrix(runNodeFull ? additionalCheckTasks : []), run_build_smoke: runNodeFull, run_check_docs: docsChanged, run_control_ui_i18n: runControlUiI18n, @@ -1539,28 +1579,7 @@ jobs: timeout-minutes: 20 strategy: fail-fast: false - matrix: - include: - - check_name: check-additional-boundaries-a - group: boundaries - boundary_shard: 1/4 - - check_name: check-additional-boundaries-b - 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 - - check_name: check-additional-extension-channels - group: extension-channels - - check_name: check-additional-extension-bundled - group: extension-bundled - - check_name: check-additional-extension-package-boundary - group: extension-package-boundary - - check_name: check-additional-runtime-topology-architecture - group: runtime-topology-architecture + matrix: ${{ fromJson(needs.preflight.outputs.additional_matrix) }} steps: - name: Checkout shell: bash @@ -1686,6 +1705,9 @@ jobs: boundaries) node scripts/run-additional-boundary-checks.mjs ;; + prompt-snapshots) + run_check "prompt:snapshots:check" pnpm prompt:snapshots:check + ;; extension-channels) run_check "lint:extensions:channels" pnpm run lint:extensions:channels ;; diff --git a/docs/ci.md b/docs/ci.md index fc34ce94996..9ca85058716 100644 --- a/docs/ci.md +++ b/docs/ci.md @@ -56,7 +56,7 @@ Scope logic lives in `scripts/ci-changed-scope.mjs` and is covered by unit tests - **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: channel contracts run as three weighted shards, 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 for manual CI and for prompt-affecting changes only, so normal unrelated Node changes do not wait behind cold prompt snapshot generation 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: channel contracts run as three weighted shards, 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. 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. diff --git a/docs/concepts/system-prompt.md b/docs/concepts/system-prompt.md index 921103289dd..b914f4a6778 100644 --- a/docs/concepts/system-prompt.md +++ b/docs/concepts/system-prompt.md @@ -137,9 +137,10 @@ collaboration-mode instructions inside the Codex runtime after OpenClaw sends thread and turn params. Regenerate them with `pnpm prompt:snapshots:gen` and verify drift with -`pnpm prompt:snapshots:check`. CI runs the drift check in the additional -boundary shard so prompt changes and snapshot updates stay attached to the same -PR. +`pnpm prompt:snapshots:check`. CI runs the drift check as a dedicated +additional check for manual CI and prompt-affecting changes so prompt changes +and snapshot updates stay attached to the same PR without slowing unrelated +boundary shards. ## Workspace bootstrap injection diff --git a/scripts/run-additional-boundary-checks.mjs b/scripts/run-additional-boundary-checks.mjs index 1c0a2fdbd28..468e7367589 100644 --- a/scripts/run-additional-boundary-checks.mjs +++ b/scripts/run-additional-boundary-checks.mjs @@ -3,7 +3,6 @@ import { spawn } from "node:child_process"; import { performance } from "node:perf_hooks"; export const BOUNDARY_CHECKS = [ - ["prompt:snapshots:check", "pnpm", ["prompt:snapshots:check"]], ["plugin-extension-boundary", "pnpm", ["run", "lint:plugins:no-extension-imports"]], ["lint:tmp:no-random-messaging", "pnpm", ["run", "lint:tmp:no-random-messaging"]], ["lint:tmp:channel-agnostic-boundaries", "pnpm", ["run", "lint:tmp:channel-agnostic-boundaries"]], @@ -58,6 +57,11 @@ export const BOUNDARY_CHECKS = [ ].map(([label, command, args]) => ({ label, command, args })); export const PROMPT_SNAPSHOT_CHECK_LABEL = "prompt:snapshots:check"; +export const PROMPT_SNAPSHOT_CHECK = { + label: PROMPT_SNAPSHOT_CHECK_LABEL, + command: "pnpm", + args: ["prompt:snapshots:check"], +}; export function resolveConcurrency(value, fallback = 4) { const parsed = Number.parseInt(String(value ?? ""), 10); diff --git a/test/scripts/run-additional-boundary-checks.test.ts b/test/scripts/run-additional-boundary-checks.test.ts index caa8a0ae04b..f0ccc805d23 100644 --- a/test/scripts/run-additional-boundary-checks.test.ts +++ b/test/scripts/run-additional-boundary-checks.test.ts @@ -1,6 +1,7 @@ import { describe, expect, it } from "vitest"; import { BOUNDARY_CHECKS, + PROMPT_SNAPSHOT_CHECK, filterChecksForEnvironment, formatCommand, parseShardSpec, @@ -23,22 +24,19 @@ function createOutputBuffer() { } describe("run-additional-boundary-checks", () => { - it("runs prompt snapshot drift checks in CI", () => { - expect(BOUNDARY_CHECKS).toContainEqual({ + it("keeps prompt snapshot drift checks as a dedicated CI check", () => { + expect(PROMPT_SNAPSHOT_CHECK).toEqual({ label: "prompt:snapshots:check", command: "pnpm", args: ["prompt:snapshots:check"], }); + expect(BOUNDARY_CHECKS.map((check) => check.label)).not.toContain("prompt:snapshots:check"); }); - it("skips prompt snapshot drift checks when preflight says they are unrelated", () => { + it("leaves boundary checks unchanged when prompt snapshots are unrelated", () => { expect( filterChecksForEnvironment(BOUNDARY_CHECKS, { OPENCLAW_RUN_PROMPT_SNAPSHOTS: "false" }), - ).not.toContainEqual({ - label: "prompt:snapshots:check", - command: "pnpm", - args: ["prompt:snapshots:check"], - }); + ).toEqual(BOUNDARY_CHECKS); }); it("normalizes concurrency input", () => {