diff --git a/.agents/skills/openclaw-testing/SKILL.md b/.agents/skills/openclaw-testing/SKILL.md index d0fa82451d9..4bcca0534ed 100644 --- a/.agents/skills/openclaw-testing/SKILL.md +++ b/.agents/skills/openclaw-testing/SKILL.md @@ -143,10 +143,11 @@ artifact reuse, and sharding instead. The parent verifier job appends slowest-job tables for child runs; rerun only that verifier after a child rerun turns green. -Standalone manual `CI` dispatches do not run the plugin prerelease suite. That -suite is intentionally reserved for the Full Release Validation CI child so PRs, -main pushes, and ad hoc broad CI checks do not spend Docker/package time on -release-only plugin product coverage. +Standalone manual `CI` dispatches do not run the plugin prerelease suite, the +extension batch sweep, or the release-only `agentic-plugins` Vitest shard. Those +lanes are intentionally reserved for the Full Release Validation CI child so +PRs, main pushes, and ad hoc broad CI checks do not spend Docker/package time or +all-plugin runtime time on release-only product coverage. If a full run is already active on a newer `origin/main`, prefer watching that run over dispatching a duplicate. If you accidentally dispatch a stale duplicate, diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ddd88e80e93..658e908844e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -199,6 +199,8 @@ jobs: : process.env.OPENCLAW_CI_CHECKOUT_REVISION; let runPluginPrereleaseSuite = isFullReleaseValidationCiRun && runNodeFull && isCanonicalRepository; + const runReleaseOnlyPluginSuites = + isFullReleaseValidationCiRun && runNodeFull && isCanonicalRepository; let pluginPrereleasePlan = { staticChecks: [], dockerLanes: [] }; if (runPluginPrereleaseSuite) { try { @@ -228,7 +230,7 @@ jobs: ? DEFAULT_EXTENSION_TEST_SHARD_COUNT : Math.max(DEFAULT_EXTENSION_TEST_SHARD_COUNT, 36); const extensionShardMatrix = createMatrix( - runNodeFull + runReleaseOnlyPluginSuites ? createExtensionTestShards({ shardCount: extensionTestShardCount, }).map((shard) => ({ @@ -271,7 +273,9 @@ jobs: } const nodeTestShards = runNodeFull - ? createNodeTestShards().map((shard) => ({ + ? createNodeTestShards({ + includeReleaseOnlyPluginShards: runReleaseOnlyPluginSuites, + }).map((shard) => ({ check_name: shard.checkName, runtime: "node", task: "test-shard", diff --git a/docs/help/testing.md b/docs/help/testing.md index 219cdacfa60..4ada3e0a9a7 100644 --- a/docs/help/testing.md +++ b/docs/help/testing.md @@ -408,6 +408,7 @@ Think of the suites as “increasing realism” (and increasing flakiness/cost): - Import-light unit tests from agents, commands, plugins, auto-reply helpers, `plugin-sdk`, and similar pure utility areas route through the `unit-fast` lane, which skips `test/setup-openclaw-runtime.ts`; stateful/runtime-heavy files stay on the existing lanes. - Selected `plugin-sdk` and `commands` helper source files also map changed-mode runs to explicit sibling tests in those light lanes, so helper edits avoid rerunning the full heavy suite for that directory. - `auto-reply` has dedicated buckets for top-level core helpers, top-level `reply.*` integration tests, and the `src/auto-reply/reply/**` subtree. CI further splits the reply subtree into agent-runner, dispatch, and commands/state-routing shards so one import-heavy bucket does not own the full Node tail. + - Normal PR/main CI intentionally skips the extension batch sweep and release-only `agentic-plugins` shard. Full Release Validation dispatches the CI child with `full_release_validation=true`, which turns those plugin/extension-heavy suites back on for release candidates. diff --git a/scripts/lib/ci-node-test-plan.mjs b/scripts/lib/ci-node-test-plan.mjs index 4e3b78e3714..69c23fc6846 100644 --- a/scripts/lib/ci-node-test-plan.mjs +++ b/scripts/lib/ci-node-test-plan.mjs @@ -10,6 +10,7 @@ const EXCLUDED_FULL_SUITE_SHARDS = new Set([ ]); const EXCLUDED_PROJECT_CONFIGS = new Set(["test/vitest/vitest.channels.config.ts"]); +const RELEASE_ONLY_PLUGIN_SHARDS = new Set(["agentic-plugins"]); function listTestFiles(rootDir) { if (!existsSync(rootDir)) { return []; @@ -288,7 +289,9 @@ function formatNodeTestShardCheckName(shardName) { return `checks-node-${normalizedShardName}`; } -export function createNodeTestShards() { +export function createNodeTestShards(options = {}) { + const includeReleaseOnlyPluginShards = options.includeReleaseOnlyPluginShards ?? true; + return fullSuiteVitestShards.flatMap((shard) => { if (EXCLUDED_FULL_SUITE_SHARDS.has(shard.config)) { return []; @@ -302,6 +305,13 @@ export function createNodeTestShards() { const splitShards = SPLIT_NODE_SHARDS.get(shard.name); if (splitShards) { return splitShards.flatMap((splitShard) => { + if ( + RELEASE_ONLY_PLUGIN_SHARDS.has(splitShard.shardName) && + !includeReleaseOnlyPluginShards + ) { + return []; + } + const splitConfigs = splitShard.includeExternalConfigs ? splitShard.configs : splitShard.configs.filter((config) => configs.includes(config)); diff --git a/test/scripts/ci-node-test-plan.test.ts b/test/scripts/ci-node-test-plan.test.ts index 3d185c2de65..d1d2ba15b3f 100644 --- a/test/scripts/ci-node-test-plan.test.ts +++ b/test/scripts/ci-node-test-plan.test.ts @@ -276,6 +276,13 @@ describe("scripts/lib/ci-node-test-plan.mjs", () => { ); }); + it("keeps expensive plugin shards release-only when normal CI asks for the cheaper plan", () => { + const shards = createNodeTestShards({ includeReleaseOnlyPluginShards: false }); + + expect(shards.some((shard) => shard.shardName === "agentic-plugins")).toBe(false); + expect(shards.some((shard) => shard.shardName === "agentic-plugin-sdk")).toBe(true); + }); + it("splits auto-reply into balanced core/top-level and reply subtree shards", () => { const shards = createNodeTestShards(); const autoReplyShards = shards