diff --git a/docs/ci.md b/docs/ci.md index 54e515a8b8f..dc221a61c07 100644 --- a/docs/ci.md +++ b/docs/ci.md @@ -46,6 +46,7 @@ Jobs are ordered so cheap checks fail before expensive ones run: Scope logic lives in `scripts/ci-changed-scope.mjs` and is covered by unit tests in `src/scripts/ci-changed-scope.test.ts`. 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. +Windows Node checks are scoped to runtime, package, config, and workflow surfaces; test-only changes stay on the Linux Node lanes so they do not reserve a 16-vCPU Windows worker for coverage that is already exercised by the normal test shards. The separate `install-smoke` workflow reuses the same scope script through its own `preflight` job. It computes `run_install_smoke` from the narrower changed-smoke signal, so Docker/install smoke only runs for install, packaging, and container-relevant changes. Its QR package smoke forces the Docker `pnpm install` layer to rerun while preserving the BuildKit pnpm store cache, so it still exercises installation without redownloading dependencies on every run. Its gateway-network e2e reuses the runtime image built earlier in the job, so it adds real container-to-container WebSocket coverage without adding another Docker build. Local changed-lane logic lives in `scripts/changed-lanes.mjs` and is executed by `scripts/check-changed.mjs`. That local gate is stricter about architecture boundaries than the broad CI platform scope: core production changes run core prod typecheck plus core tests, core test-only changes run only core test typecheck/tests, extension production changes run extension prod typecheck plus extension tests, and extension test-only changes run only extension test typecheck/tests. Public Plugin SDK or plugin-contract changes expand to extension validation because extensions depend on those core contracts. Release metadata-only version bumps run targeted version/config/root-dependency checks. Unknown root/config changes fail safe to all lanes. diff --git a/scripts/ci-changed-scope.mjs b/scripts/ci-changed-scope.mjs index 28f39bcc8d2..670b06b6c61 100644 --- a/scripts/ci-changed-scope.mjs +++ b/scripts/ci-changed-scope.mjs @@ -15,6 +15,8 @@ const NODE_SCOPE_RE = /^(src\/|test\/|extensions\/|packages\/|scripts\/|ui\/|\.github\/|openclaw\.mjs$|package\.json$|pnpm-lock\.yaml$|pnpm-workspace\.yaml$|tsconfig.*\.json$|vitest.*\.ts$|tsdown\.config\.ts$|\.oxlintrc\.json$|\.oxfmtrc\.jsonc$)/; const WINDOWS_SCOPE_RE = /^(src\/|test\/|extensions\/|packages\/|scripts\/|ui\/|openclaw\.mjs$|package\.json$|pnpm-lock\.yaml$|pnpm-workspace\.yaml$|tsconfig.*\.json$|vitest.*\.ts$|tsdown\.config\.ts$|\.github\/actions\/setup-node-env\/action\.yml$|\.github\/actions\/setup-pnpm-store-cache\/action\.yml$)/; +const TEST_ONLY_PATH_RE = + /(^test\/|\/test\/|\/tests\/|(?:^|\/)[^/]+\.(?:test|spec|test-utils|test-support|test-harness|e2e-harness)\.[cm]?[jt]sx?$)/; const CONTROL_UI_I18N_SCOPE_RE = /^(ui\/src\/i18n\/|scripts\/control-ui-i18n\.ts$|\.github\/workflows\/control-ui-locale-refresh\.yml$)/; const NATIVE_ONLY_RE = @@ -81,7 +83,7 @@ export function detectChangedScope(changedPaths) { runNode = true; } - if (WINDOWS_SCOPE_RE.test(path)) { + if (WINDOWS_SCOPE_RE.test(path) && !TEST_ONLY_PATH_RE.test(path)) { runWindows = true; } diff --git a/src/scripts/ci-changed-scope.test.ts b/src/scripts/ci-changed-scope.test.ts index e0f4dca00c2..17e99feeb64 100644 --- a/src/scripts/ci-changed-scope.test.ts +++ b/src/scripts/ci-changed-scope.test.ts @@ -182,6 +182,27 @@ describe("detectChangedScope", () => { }); }); + it("does not run Windows for test-only changes", () => { + expect(detectChangedScope(["extensions/memory-lancedb/index.test.ts"])).toEqual({ + runNode: true, + runMacos: false, + runAndroid: false, + runWindows: false, + runSkillsPython: false, + runChangedSmoke: false, + runControlUiI18n: false, + }); + expect(detectChangedScope(["test/helpers/windows-paths.test-utils.ts"])).toEqual({ + runNode: true, + runMacos: false, + runAndroid: false, + runWindows: false, + runSkillsPython: false, + runChangedSmoke: false, + runControlUiI18n: false, + }); + }); + it("runs changed-smoke for install and packaging surfaces", () => { expect(detectChangedScope(["scripts/install.sh"])).toEqual({ runNode: true,