From 28839dfd7fd5c16bf90496fbf830f98f60e49f43 Mon Sep 17 00:00:00 2001 From: Roomote Date: Wed, 16 Sep 2026 03:18:19 +0000 Subject: [PATCH 1/2] ci: reduce Windows unit-test cold starts --- .github/workflows/code-qa.yml | 36 ++++++++++++++++++----- package.json | 1 + scripts/code-qa-workflow.test.mjs | 49 +++++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 7 deletions(-) create mode 100644 scripts/code-qa-workflow.test.mjs diff --git a/.github/workflows/code-qa.yml b/.github/workflows/code-qa.yml index f1ba2a10cb..017471a627 100644 --- a/.github/workflows/code-qa.yml +++ b/.github/workflows/code-qa.yml @@ -90,6 +90,8 @@ jobs: run: pnpm lint - name: Check types run: pnpm check-types + - name: Validate Code QA workflow + run: pnpm test:code-qa-ci - name: Model-check task lifecycle protocols run: pnpm lifecycle:model-check - name: Validate MCP OAuth integration @@ -135,11 +137,11 @@ jobs: - os: ubuntu-latest name: ubuntu-latest codecov-flag: ubuntu - upload-coverage: true + collect-coverage: true - os: windows-latest name: windows-latest codecov-flag: windows - upload-coverage: false + collect-coverage: false steps: - name: Checkout code uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -155,18 +157,36 @@ jobs: ${{ runner.os }}-turbo-${{ hashFiles('**/pnpm-lock.yaml') }}- ${{ runner.os }}-turbo- - name: Run non-extension package coverage + if: matrix.collect-coverage run: pnpm turbo run test:coverage --filter="!@roo-code/core" --filter="!zoo-code" --log-order grouped --output-logs new-only + - name: Run non-extension package tests + if: ${{ !matrix.collect-coverage }} + run: pnpm turbo run test --filter="!@roo-code/core" --filter="!zoo-code" --log-order grouped --output-logs new-only - name: Run extension coverage lanes + if: matrix.collect-coverage run: pnpm turbo run test:coverage:api test:coverage:core test:coverage:services test:coverage:misc test:coverage:tree-sitter --filter="zoo-code" --concurrency=2 --log-order grouped --output-logs new-only + - name: Run extension unit tests + if: ${{ !matrix.collect-coverage }} + run: pnpm turbo run test:unit --filter="zoo-code" --log-order grouped --output-logs new-only - name: Verify extension coverage contract + if: matrix.collect-coverage run: pnpm --dir src run verify:coverage-contract - name: Run extension dist smoke test run: pnpm turbo run test:dist --filter="zoo-code" --log-order grouped --output-logs new-only - name: Run core unit coverage + if: matrix.collect-coverage run: pnpm turbo run test:coverage:unit --filter="@roo-code/core" --log-order grouped --output-logs new-only + - name: Run core unit tests + if: ${{ !matrix.collect-coverage }} + run: pnpm turbo run test:unit --filter="@roo-code/core" --log-order grouped --output-logs new-only - name: Run core integration coverage + if: matrix.collect-coverage run: pnpm turbo run test:coverage:integration --filter="@roo-code/core" --log-order grouped --output-logs new-only + - name: Run core integration tests + if: ${{ !matrix.collect-coverage }} + run: pnpm turbo run test:integration --filter="@roo-code/core" --log-order grouped --output-logs new-only - name: Verify extension coverage reports + if: matrix.collect-coverage run: | node src/scripts/verify-lcov.mjs src/coverage/api/lcov.info node src/scripts/verify-lcov.mjs src/coverage/core/lcov.info @@ -174,12 +194,14 @@ jobs: node src/scripts/verify-lcov.mjs src/coverage/misc/lcov.info node src/scripts/verify-lcov.mjs src/coverage/tree-sitter/lcov.info - name: Merge extension coverage reports + if: matrix.collect-coverage run: | mkdir -p src/coverage/merged pnpm --dir src run merge:coverage node src/scripts/verify-lcov.mjs src/coverage/merged/lcov.info # Validate cache boundaries before publishing any new Turbo entries. - name: Verify coverage cache inputs + if: matrix.collect-coverage run: pnpm --dir src run verify:coverage-cache-inputs - name: Save Turbo cache if: steps.turbo-cache.outputs.cache-hit != 'true' @@ -197,7 +219,7 @@ jobs: # it. Core and webview reports retain their independent flags. # See https://docs.codecov.com/docs/flags - name: Upload non-core coverage to Codecov - if: matrix.upload-coverage + if: matrix.collect-coverage uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 with: files: >- @@ -209,7 +231,7 @@ jobs: flags: ${{ matrix.codecov-flag }} token: ${{ secrets.CODECOV_TOKEN }} - name: Upload webview JSDOM coverage to Codecov - if: matrix.upload-coverage + if: matrix.collect-coverage uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 with: files: webview-ui/coverage/lcov.info @@ -217,7 +239,7 @@ jobs: flags: webview-ui token: ${{ secrets.CODECOV_TOKEN }} - name: Upload core unit coverage to Codecov - if: matrix.upload-coverage + if: matrix.collect-coverage uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 with: files: packages/core/coverage/unit/lcov.info @@ -225,7 +247,7 @@ jobs: flags: ${{ matrix.codecov-flag }},core-unit token: ${{ secrets.CODECOV_TOKEN }} - name: Upload core integration coverage to Codecov - if: matrix.upload-coverage + if: matrix.collect-coverage uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 with: files: packages/core/coverage/integration/lcov.info @@ -233,7 +255,7 @@ jobs: flags: ${{ matrix.codecov-flag }},core-integration token: ${{ secrets.CODECOV_TOKEN }} - name: Upload coverage reports to GitHub - if: matrix.upload-coverage + if: matrix.collect-coverage uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: coverage-reports-${{ matrix.name }} diff --git a/package.json b/package.json index df3410bbc1..dde5222517 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "lint": "turbo lint --log-order grouped --output-logs new-only", "check-types": "turbo check-types --log-order grouped --output-logs new-only", "test": "turbo test --log-order grouped --output-logs new-only", + "test:code-qa-ci": "node --test scripts/code-qa-workflow.test.mjs", "test:mutation-ci": "node --test scripts/stryker-diff.test.mjs", "lifecycle:model-check": "tsx scripts/check-task-lifecycle.ts && tsx scripts/check-task-store-concurrency.ts && tsx scripts/check-provider-handoff-scheduler.ts && pnpm cleanup-protocol:model-check && pnpm parser-scope:model-check && tsx scripts/check-completion-persistence.ts && tsx scripts/check-delegated-mode-readers.ts", "cleanup-protocol:model-check": "tsx scripts/check-task-cleanup-protocol.ts", diff --git a/scripts/code-qa-workflow.test.mjs b/scripts/code-qa-workflow.test.mjs new file mode 100644 index 0000000000..7739aa059f --- /dev/null +++ b/scripts/code-qa-workflow.test.mjs @@ -0,0 +1,49 @@ +import assert from "node:assert/strict" +import fs from "node:fs" +import path from "node:path" +import { describe, it } from "node:test" +import { fileURLToPath } from "node:url" + +const repositoryRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..") +const workflow = fs.readFileSync(path.join(repositoryRoot, ".github/workflows/code-qa.yml"), "utf8") + +const workflowStep = (name) => { + const match = workflow.match(new RegExp(`- name: ${name}\\n(?(?:\\s{14,}.*\\n?)*)`)) + assert.ok(match?.groups?.body, `missing workflow step: ${name}`) + return match.groups.body +} + +describe("platform unit-test workflow", () => { + it("keeps coverage authoritative on Ubuntu and runs equivalent uninstrumented Windows tests", () => { + assert.match(workflow, /name: ubuntu-latest[\s\S]*?collect-coverage: true/) + assert.match(workflow, /name: windows-latest[\s\S]*?collect-coverage: false/) + + for (const lane of ["api", "core", "services", "misc", "tree-sitter"]) { + assert.ok(workflow.includes(`test:coverage:${lane}`), `missing ${lane} coverage lane`) + } + + assert.ok(workflow.includes('test:coverage --filter="!@roo-code/core" --filter="!zoo-code"')) + assert.ok(workflow.includes('test --filter="!@roo-code/core" --filter="!zoo-code"')) + assert.ok(workflow.includes('test:unit --filter="zoo-code"')) + assert.ok(workflow.includes('test:coverage:unit --filter="@roo-code/core"')) + assert.ok(workflow.includes('test:unit --filter="@roo-code/core"')) + assert.ok(workflow.includes('test:coverage:integration --filter="@roo-code/core"')) + assert.ok(workflow.includes('test:integration --filter="@roo-code/core"')) + }) + + it("does not run coverage verification or uploads on Windows", () => { + for (const stepName of [ + "Verify extension coverage contract", + "Verify extension coverage reports", + "Merge extension coverage reports", + "Verify coverage cache inputs", + "Upload non-core coverage to Codecov", + "Upload webview JSDOM coverage to Codecov", + "Upload core unit coverage to Codecov", + "Upload core integration coverage to Codecov", + "Upload coverage reports to GitHub", + ]) { + assert.match(workflowStep(stepName), /if: matrix\.collect-coverage/) + } + }) +}) From 7a88e978d641d0cb7a7746cfd94ebf20c15ec5de Mon Sep 17 00:00:00 2001 From: Roomote Date: Wed, 16 Sep 2026 03:40:13 +0000 Subject: [PATCH 2/2] fix(ci): preserve Windows Turbo cache boundaries --- .github/workflows/code-qa.yml | 16 +----- scripts/code-qa-workflow.test.mjs | 38 +++++++++---- src/package.json | 5 ++ src/turbo.json | 95 +++++++++++++++++++++++++++++++ 4 files changed, 130 insertions(+), 24 deletions(-) diff --git a/.github/workflows/code-qa.yml b/.github/workflows/code-qa.yml index 017471a627..500fd0bf81 100644 --- a/.github/workflows/code-qa.yml +++ b/.github/workflows/code-qa.yml @@ -157,34 +157,22 @@ jobs: ${{ runner.os }}-turbo-${{ hashFiles('**/pnpm-lock.yaml') }}- ${{ runner.os }}-turbo- - name: Run non-extension package coverage - if: matrix.collect-coverage run: pnpm turbo run test:coverage --filter="!@roo-code/core" --filter="!zoo-code" --log-order grouped --output-logs new-only - - name: Run non-extension package tests - if: ${{ !matrix.collect-coverage }} - run: pnpm turbo run test --filter="!@roo-code/core" --filter="!zoo-code" --log-order grouped --output-logs new-only - name: Run extension coverage lanes if: matrix.collect-coverage run: pnpm turbo run test:coverage:api test:coverage:core test:coverage:services test:coverage:misc test:coverage:tree-sitter --filter="zoo-code" --concurrency=2 --log-order grouped --output-logs new-only - - name: Run extension unit tests + - name: Run extension test lanes if: ${{ !matrix.collect-coverage }} - run: pnpm turbo run test:unit --filter="zoo-code" --log-order grouped --output-logs new-only + run: pnpm turbo run test:api test:core test:services test:misc test:tree-sitter --filter="zoo-code" --concurrency=2 --log-order grouped --output-logs new-only - name: Verify extension coverage contract if: matrix.collect-coverage run: pnpm --dir src run verify:coverage-contract - name: Run extension dist smoke test run: pnpm turbo run test:dist --filter="zoo-code" --log-order grouped --output-logs new-only - name: Run core unit coverage - if: matrix.collect-coverage run: pnpm turbo run test:coverage:unit --filter="@roo-code/core" --log-order grouped --output-logs new-only - - name: Run core unit tests - if: ${{ !matrix.collect-coverage }} - run: pnpm turbo run test:unit --filter="@roo-code/core" --log-order grouped --output-logs new-only - name: Run core integration coverage - if: matrix.collect-coverage run: pnpm turbo run test:coverage:integration --filter="@roo-code/core" --log-order grouped --output-logs new-only - - name: Run core integration tests - if: ${{ !matrix.collect-coverage }} - run: pnpm turbo run test:integration --filter="@roo-code/core" --log-order grouped --output-logs new-only - name: Verify extension coverage reports if: matrix.collect-coverage run: | diff --git a/scripts/code-qa-workflow.test.mjs b/scripts/code-qa-workflow.test.mjs index 7739aa059f..b8ba47e362 100644 --- a/scripts/code-qa-workflow.test.mjs +++ b/scripts/code-qa-workflow.test.mjs @@ -6,6 +6,7 @@ import { fileURLToPath } from "node:url" const repositoryRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..") const workflow = fs.readFileSync(path.join(repositoryRoot, ".github/workflows/code-qa.yml"), "utf8") +const extensionTurbo = JSON.parse(fs.readFileSync(path.join(repositoryRoot, "src/turbo.json"), "utf8")) const workflowStep = (name) => { const match = workflow.match(new RegExp(`- name: ${name}\\n(?(?:\\s{14,}.*\\n?)*)`)) @@ -17,18 +18,25 @@ describe("platform unit-test workflow", () => { it("keeps coverage authoritative on Ubuntu and runs equivalent uninstrumented Windows tests", () => { assert.match(workflow, /name: ubuntu-latest[\s\S]*?collect-coverage: true/) assert.match(workflow, /name: windows-latest[\s\S]*?collect-coverage: false/) + assert.ok(!workflow.includes("matrix.upload-coverage")) - for (const lane of ["api", "core", "services", "misc", "tree-sitter"]) { - assert.ok(workflow.includes(`test:coverage:${lane}`), `missing ${lane} coverage lane`) - } + const extensionCoverage = workflowStep("Run extension coverage lanes") + assert.match(extensionCoverage, /if: matrix\.collect-coverage/) + assert.ok(extensionCoverage.includes("test:coverage:api test:coverage:core")) + + const extensionTests = workflowStep("Run extension test lanes") + assert.match(extensionTests, /if: \$\{\{ !matrix\.collect-coverage \}\}/) + assert.ok(extensionTests.includes("test:api test:core test:services test:misc test:tree-sitter")) - assert.ok(workflow.includes('test:coverage --filter="!@roo-code/core" --filter="!zoo-code"')) - assert.ok(workflow.includes('test --filter="!@roo-code/core" --filter="!zoo-code"')) - assert.ok(workflow.includes('test:unit --filter="zoo-code"')) - assert.ok(workflow.includes('test:coverage:unit --filter="@roo-code/core"')) - assert.ok(workflow.includes('test:unit --filter="@roo-code/core"')) - assert.ok(workflow.includes('test:coverage:integration --filter="@roo-code/core"')) - assert.ok(workflow.includes('test:integration --filter="@roo-code/core"')) + for (const [stepName, command] of [ + ["Run non-extension package coverage", 'test:coverage --filter="!@roo-code/core" --filter="!zoo-code"'], + ["Run core unit coverage", 'test:coverage:unit --filter="@roo-code/core"'], + ["Run core integration coverage", 'test:coverage:integration --filter="@roo-code/core"'], + ]) { + const body = workflowStep(stepName) + assert.ok(!body.includes("if:"), `${stepName} must retain its cache-compatible task on both platforms`) + assert.ok(body.includes(command), `missing command in step: ${stepName}`) + } }) it("does not run coverage verification or uploads on Windows", () => { @@ -46,4 +54,14 @@ describe("platform unit-test workflow", () => { assert.match(workflowStep(stepName), /if: matrix\.collect-coverage/) } }) + + it("keeps plain extension lanes aligned with coverage cache boundaries", () => { + for (const lane of ["api", "core", "services", "misc", "tree-sitter"]) { + const plainTask = extensionTurbo.tasks[`test:${lane}`] + const coverageTask = extensionTurbo.tasks[`test:coverage:${lane}`] + + assert.deepEqual(plainTask.dependsOn, coverageTask.dependsOn) + assert.deepEqual(plainTask.inputs, coverageTask.inputs) + } + }) }) diff --git a/src/package.json b/src/package.json index e047f661d8..a6fe40b8e6 100644 --- a/src/package.json +++ b/src/package.json @@ -447,6 +447,11 @@ "merge:coverage": "node scripts/merge-lcov.mjs coverage/merged/lcov.info coverage/api/lcov.info coverage/core/lcov.info coverage/services/lcov.info coverage/misc/lcov.info coverage/tree-sitter/lcov.info", "test:unit": "vitest run --config vitest.unit.config.ts", "test:dist": "vitest run --config vitest.dist.config.ts", + "test:api": "vitest run --config vitest.api.config.ts", + "test:core": "vitest run --config vitest.core.config.ts", + "test:services": "vitest run --config vitest.services.config.ts", + "test:misc": "vitest run --config vitest.misc.config.ts", + "test:tree-sitter": "vitest run --config vitest.tree-sitter.config.ts", "test:coverage": "vitest run --coverage", "test:coverage:unit": "vitest run --config vitest.unit.config.ts --coverage", "test:coverage:api": "vitest run --config vitest.api.config.ts --coverage", diff --git a/src/turbo.json b/src/turbo.json index eac6f30f3c..9b5fe94f84 100644 --- a/src/turbo.json +++ b/src/turbo.json @@ -120,6 +120,101 @@ ], "outputs": ["coverage/tree-sitter/**"] }, + "test:api": { + "dependsOn": ["^build"], + "inputs": [ + "$TURBO_DEFAULT$", + "!scripts/verify-coverage-cache-inputs.mjs", + "!scripts/coverage-contract.mjs", + "!scripts/verify-coverage-contract.mjs", + "!scripts/verify-lcov.mjs", + "!core/**/*.{test,spec}.{ts,tsx}", + "!services/**/*.{test,spec}.{ts,tsx}", + "!__tests__/**/*.{test,spec}.{ts,tsx}", + "!activate/**/*.{test,spec}.{ts,tsx}", + "!extension/**/*.{test,spec}.{ts,tsx}", + "!i18n/**/*.{test,spec}.{ts,tsx}", + "!integrations/**/*.{test,spec}.{ts,tsx}", + "!scripts/**/*.{test,spec}.{mjs,ts}", + "!shared/**/*.{test,spec}.{ts,tsx}", + "!test-utils/**/*.{test,spec}.{ts,tsx}", + "!utils/**/*.{test,spec}.{ts,tsx}" + ] + }, + "test:core": { + "dependsOn": ["^build"], + "inputs": [ + "$TURBO_DEFAULT$", + "!scripts/verify-coverage-cache-inputs.mjs", + "!scripts/coverage-contract.mjs", + "!scripts/verify-coverage-contract.mjs", + "!scripts/verify-lcov.mjs", + "!api/**/*.{test,spec}.{ts,tsx}", + "!services/**/*.{test,spec}.{ts,tsx}", + "!__tests__/**/*.{test,spec}.{ts,tsx}", + "!activate/**/*.{test,spec}.{ts,tsx}", + "!extension/**/*.{test,spec}.{ts,tsx}", + "!i18n/**/*.{test,spec}.{ts,tsx}", + "!integrations/**/*.{test,spec}.{ts,tsx}", + "!scripts/**/*.{test,spec}.{mjs,ts}", + "!shared/**/*.{test,spec}.{ts,tsx}", + "!test-utils/**/*.{test,spec}.{ts,tsx}", + "!utils/**/*.{test,spec}.{ts,tsx}" + ] + }, + "test:services": { + "dependsOn": ["^build"], + "inputs": [ + "$TURBO_DEFAULT$", + "!scripts/verify-coverage-cache-inputs.mjs", + "!scripts/coverage-contract.mjs", + "!scripts/verify-coverage-contract.mjs", + "!scripts/verify-lcov.mjs", + "!api/**/*.{test,spec}.{ts,tsx}", + "!core/**/*.{test,spec}.{ts,tsx}", + "!services/tree-sitter/**/*.{test,spec}.{ts,tsx}", + "!__tests__/**/*.{test,spec}.{ts,tsx}", + "!activate/**/*.{test,spec}.{ts,tsx}", + "!extension/**/*.{test,spec}.{ts,tsx}", + "!i18n/**/*.{test,spec}.{ts,tsx}", + "!integrations/**/*.{test,spec}.{ts,tsx}", + "!scripts/**/*.{test,spec}.{mjs,ts}", + "!shared/**/*.{test,spec}.{ts,tsx}", + "!test-utils/**/*.{test,spec}.{ts,tsx}", + "!utils/**/*.{test,spec}.{ts,tsx}" + ] + }, + "test:misc": { + "dependsOn": ["^build"], + "inputs": [ + "$TURBO_DEFAULT$", + "!scripts/verify-coverage-cache-inputs.mjs", + "!scripts/coverage-contract.mjs", + "!scripts/verify-coverage-contract.mjs", + "!scripts/verify-lcov.mjs", + "!api/**/*.{test,spec}.{ts,tsx}", + "!core/**/*.{test,spec}.{ts,tsx}", + "!services/**/*.{test,spec}.{ts,tsx}", + "!__tests__/dist_assets.spec.ts" + ] + }, + "test:tree-sitter": { + "dependsOn": ["@roo-code/types#build"], + "inputs": [ + "services/tree-sitter/**", + "services/shared/fallback-extensions.ts", + "core/ignore/RooIgnoreController.ts", + "utils/fs.ts", + "utils/path.ts", + "utils/vitest-verbosity.ts", + "__mocks__/**", + "vitest.setup.ts", + "vitest.config.ts", + "vitest.tree-sitter.config.ts", + "package.json", + "tsconfig.json" + ] + }, "bundle": { "dependsOn": ["^build", "@roo-code/vscode-webview#build"], "outputs": ["dist/**"]