From 63dcd40724e594f7f2dbb824e06cd30b82f37dac Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Thu, 27 Aug 2026 00:45:54 -0700 Subject: [PATCH] fix(ci): scope maintainer activity helper checks (#130790) --- scripts/lib/changed-path-facts.mjs | 3 + test/scripts/changed-lanes.test.ts | 108 +++++++++++++++--- test/scripts/changed-path-facts.test.ts | 3 + .../scripts/ci-changed-node-test-plan.test.ts | 42 ++++++- 4 files changed, 136 insertions(+), 20 deletions(-) diff --git a/scripts/lib/changed-path-facts.mjs b/scripts/lib/changed-path-facts.mjs index 8de19e28f80b..f02f05030f67 100644 --- a/scripts/lib/changed-path-facts.mjs +++ b/scripts/lib/changed-path-facts.mjs @@ -16,6 +16,9 @@ const SURFACE_PATTERNS = [ ["app", /^(?:apps\/|Swabble\/|appcast\.xml$)/u], ["rootTest", /^test\//u], ["testFixture", /^test-fixtures\//u], + // This hidden helper only reports maintainer activity; it has no product consumers. + // Match the reviewed leaf exactly so unreviewed skill executables still fail safe. + ["rootTooling", /^\.agents\/skills\/openclaw-pr-maintainer\/scripts\/github-activity\.sh$/u], [ "rootTooling", /^(?:scripts\/|test\/vitest\/|\.github\/|\.vscode\/|config\/|deploy\/|git-hooks\/|Dockerfile\.sandbox(?:-(?:browser|common))?$|Makefile$|docker-setup\.sh$|setup-podman\.sh$|openclaw\.podman\.env$|skills\/pyproject\.toml$|vitest(?:\..+)?\.config\.ts$|tsconfig.*\.json$|\.dockerignore$|\.gitignore$|\.jscpd\.json$|\.npmignore$|\.pre-commit-config\.yaml$|\.swiftformat$|\.swiftlint\.yml$|\.oxlint.*|\.oxfmt.*)/u, diff --git a/test/scripts/changed-lanes.test.ts b/test/scripts/changed-lanes.test.ts index 3f177b89e3cd..33e2c737ed55 100644 --- a/test/scripts/changed-lanes.test.ts +++ b/test/scripts/changed-lanes.test.ts @@ -45,6 +45,7 @@ import { cleanupTempDirs, makeTempDir as makeTempRepoRoot } from "../helpers/tem const tempDirs: string[] = []; const repoRoot = process.cwd(); +const githubActivityHelper = ".agents/skills/openclaw-pr-maintainer/scripts/github-activity.sh"; const tsxImport = pathToFileURL(createRequire(import.meta.url).resolve("tsx")).href; type ExecFileSyncFailure = Error & { status?: number | null; stderr?: Buffer }; const nestedGitEnvKeys = [ @@ -380,6 +381,41 @@ describe("scripts/changed-lanes", () => { ); }); + it("keeps the hidden maintainer helper trio on tooling checks through both CLIs", () => { + const paths = [ + githubActivityHelper, + ".agents/skills/openclaw-pr-maintainer/SKILL.md", + "test/scripts/github-activity-helper.test.ts", + ]; + const lanes = runChangedLanesCli(repoRoot, ["--json", "--", ...paths]); + const result = runRepoScript("scripts/check-changed.mjs", ["--dry-run", "--", ...paths]); + + expectLanes(lanes.lanes, { docs: true, testRoot: true, tooling: true }); + expect(lanes).toMatchObject({ extensionImpactFromCore: false, docsOnly: false }); + expect(lanes.paths.toSorted()).toEqual(paths.toSorted()); + expect(result.status).toBe(0); + expect(result.stderr).toContain("[check:changed:dry-run] lanes=testRoot, docs, tooling"); + const commands = result.stderr + .split("\n") + .filter((line) => line.startsWith("[check:changed:dry-run] would run: ")) + .map((line) => line.replace("[check:changed:dry-run] would run: ", "")); + expect(commands).toEqual([ + "pnpm check:no-conflict-markers", + "pnpm check:changelog-attributions", + "pnpm check:doctor-deprecation-registry", + "pnpm lint:extensions:no-guarded-wildcard-reexports", + "pnpm lint:extensions:no-plugin-sdk-wildcard-reexports", + "pnpm dup:check:coverage", + "pnpm check:coercion-helpers", + "pnpm deps:pins:check", + `pnpm format:check --no-error-on-unmatched-pattern -- ${lanes.paths.join(" ")}`, + "pnpm deps:patches:check", + "node scripts/report-test-temp-creations.mjs --base origin/main --head HEAD", + "pnpm tsgo:test:root", + "pnpm lint:scripts", + ]); + }); + it("includes untracked worktree files in the default local diff", () => { const dir = makeTempRepoRoot(tempDirs, "openclaw-changed-lanes-"); git(dir, ["init", "-q", "--initial-branch=main"]); @@ -1276,31 +1312,64 @@ describe("scripts/changed-lanes", () => { } }); - it("expands public core/plugin contracts to extension validation", () => { - const result = detectChangedLanes(["src/plugin-sdk/core.ts"]); - const plan = createChangedCheckPlan(result); + it.each([{ otherPaths: [] }, { otherPaths: [githubActivityHelper] }])( + "expands public core/plugin contracts with $otherPaths to extension validation", + ({ otherPaths }) => { + const result = detectChangedLanes(["src/plugin-sdk/core.ts", ...otherPaths]); + const plan = createChangedCheckPlan(result); - expect(result.extensionImpactFromCore).toBe(true); - expectLanes(result.lanes, { - core: true, - coreTests: true, - extensions: true, - extensionTests: true, - }); - expect(plan.commands.map((command) => command.args[0])).toContain("tsgo:core"); - expect(plan.commands.map((command) => command.args[0])).toContain("tsgo:extensions:test"); - }); + expect(result.extensionImpactFromCore).toBe(true); + expectLanes(result.lanes, { + core: true, + coreTests: true, + extensions: true, + extensionTests: true, + tooling: otherPaths.length > 0, + }); + expect(plan.commands.map((command) => command.args[0])).toEqual( + expect.arrayContaining([ + "tsgo:core", + "tsgo:core:test", + "tsgo:extensions", + "tsgo:extensions:test", + ]), + ); + }, + ); - it("fails safe for root config changes", () => { - const result = detectChangedLanes(["pnpm-lock.yaml"]); - const plan = createChangedCheckPlan(result); + it.each([ + "pnpm-lock.yaml", + ".agents/skills/openclaw-pr-maintainer/scripts/unknown-helper.sh", + `${githubActivityHelper}.bak`, + `${githubActivityHelper}/child.sh`, + `other/${githubActivityHelper}`, + ".agents/skills/openclaw-pr-maintainer-extra/scripts/github-activity.sh", + ".agents/config.json", + ".agents/skills/autoreview/scripts/autoreview", + ".agents/skills/openclaw-changelog-update/scripts/verify-release-notes.mjs", + ])("fails safe for %s even alongside the hidden maintainer helper", (changedPath) => { + for (const paths of [[changedPath], [githubActivityHelper, changedPath]]) { + const result = detectChangedLanes(paths); + const plan = createChangedCheckPlan(result); - expect(result.lanes.all).toBe(true); - expect(plan.commands.map((command) => command.args[0])).toContain("tsgo:all"); - expect(plan.commands.map((command) => command.args[0])).not.toContain("test"); + expectLanes(result.lanes, { all: true, tooling: paths.includes(githubActivityHelper) }); + expect(result.extensionImpactFromCore).toBe(true); + expect(plan.commands.map((command) => command.args[0])).toContain("tsgo:all"); + expect(plan.commands.map((command) => command.args[0])).toContain("lint"); + expect(plan.commands.map((command) => command.args[0])).not.toContain("test"); + } }); it.each([ + ...[ + githubActivityHelper, + `./${githubActivityHelper}`, + githubActivityHelper.replaceAll("/", "\\"), + ].map((helperPath) => ({ + name: `routes hidden maintainer helper ${helperPath} to tooling instead of all lanes`, + paths: [helperPath], + excludesTests: true, + })), { name: "routes gitignore changes to tooling instead of all lanes", paths: [".gitignore"], @@ -1356,6 +1425,7 @@ describe("scripts/changed-lanes", () => { const commands = createChangedCheckPlan(result).commands.map((command) => command.args[0]); expectLanes(result.lanes, { tooling: true }); + expect(result.extensionImpactFromCore).toBe(false); expect(commands).toContain("lint:scripts"); expect(commands).not.toContain("tsgo:all"); if (excludesTests) { diff --git a/test/scripts/changed-path-facts.test.ts b/test/scripts/changed-path-facts.test.ts index 80516989ebf7..95c87473c4be 100644 --- a/test/scripts/changed-path-facts.test.ts +++ b/test/scripts/changed-path-facts.test.ts @@ -17,6 +17,9 @@ describe("changed path facts", () => { ["test/scripts/changed-lanes.test.ts", "rootTest"], ["test-fixtures/sample.ts", "testFixture"], ["scripts/check-changed.mjs", "rootTooling"], + [".agents/skills/openclaw-pr-maintainer/scripts/github-activity.sh", "rootTooling"], + [".agents/skills/openclaw-pr-maintainer/SKILL.md", "docs"], + ["test/scripts/github-activity-helper.test.ts", "rootTest"], [".github/workflows/ci.yml", "rootTooling"], ["package.json", "rootGlobal"], ["assets/legacy.png", "legacyRootAsset"], diff --git a/test/scripts/ci-changed-node-test-plan.test.ts b/test/scripts/ci-changed-node-test-plan.test.ts index 2997de8582f9..1ba05ba6aeea 100644 --- a/test/scripts/ci-changed-node-test-plan.test.ts +++ b/test/scripts/ci-changed-node-test-plan.test.ts @@ -17,11 +17,15 @@ import { listExtensionTestFilesForRoots, resolveExtensionTestConfig, } from "../../scripts/lib/extension-test-plan.mts"; -import { hasImportGraphImpactOnTargets } from "../../scripts/test-projects.test-support.mts"; +import { + hasImportGraphImpactOnTargets, + resolveChangedTestTargetPlan, +} from "../../scripts/test-projects.test-support.mts"; import { listGitTrackedFiles } from "../../src/test-utils/repo-files.js"; import { isGatewayServerTestFile } from "../vitest/vitest.gateway-server-paths.mjs"; const CODEX_TEST_PROCESS_FILE_LIMIT = 12; +const githubActivityHelper = ".agents/skills/openclaw-pr-maintainer/scripts/github-activity.sh"; function expectBoundedCodexFallback( shards: ReturnType, @@ -402,6 +406,42 @@ describe("CI changed Node test plan", () => { expect(createChangedExtensionFallbackShards(["docs/ci.md"])).toEqual([]); }); + it.each([ + { name: "helper alone", changedPaths: [githubActivityHelper] }, + { + name: "helper trio", + changedPaths: [ + githubActivityHelper, + ".agents/skills/openclaw-pr-maintainer/SKILL.md", + "test/scripts/github-activity-helper.test.ts", + ], + }, + ])( + "keeps hidden maintainer helper targets and compact core fallback for $name", + ({ changedPaths }) => { + expect(hasCoreExtensionImpact(changedPaths)).toBe(false); + expect(createChangedExtensionFallbackShards(changedPaths)).toEqual([]); + expect(resolveChangedTestTargetPlan(changedPaths, { broad: true })).toMatchObject({ + mode: "targets", + targets: expect.arrayContaining(["test/scripts/github-activity-helper.test.ts"]), + }); + expect(createChangedNodeTestShards(changedPaths)).toBeNull(); + }, + ); + + it.each([ + "src/plugin-sdk/core.ts", + ".agents/skills/openclaw-pr-maintainer/scripts/unknown-helper.sh", + ])( + "retains all extension configs for the hidden maintainer helper mixed with %s", + (changedPath) => { + const paths = [githubActivityHelper, changedPath]; + expect(hasCoreExtensionImpact(paths)).toBe(true); + expect(createChangedNodeTestShards(paths)).toBeNull(); + expectAllExtensionConfigs(createChangedExtensionFallbackShards(paths)); + }, + ); + it.each([ { changedPath: "extensions/browser/src/browser/cdp.helpers.test.ts",