Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
f2ad040
chore(ci): use cacheable extension test lanes
roomote Sep 12, 2026
5129761
fix(ci): prepare tree-sitter WASMs for coverage
roomote Sep 12, 2026
54581c8
fix(ci): require covered lines in LCOV guard
roomote Sep 12, 2026
795e8c0
fix(ci): publish coverage prerequisites atomically
roomote Sep 12, 2026
af97e9d
test(ci): enforce coverage lane contract
roomote Sep 12, 2026
d6a67e9
fix(ci): isolate coverage contract cache
roomote Sep 12, 2026
89a338e
fix(ci): verify cached WASM restoration
roomote Sep 12, 2026
75d19fa
fix(ci): support coverage verifier on Windows
roomote Sep 12, 2026
f69e1fc
fix(ci): require complete LCOV line summaries
roomote Sep 12, 2026
55c60fc
fix(ci): validate LCOV line totals
roomote Sep 12, 2026
dd42be5
fix(ci): reject duplicate LCOV summaries
roomote Sep 12, 2026
a857936
fix(ci): reject empty LCOV source paths
roomote Sep 13, 2026
e38cb20
fix(ci): preserve verified WASM artifacts
roomote Sep 13, 2026
6e39677
test(ci): cover LCOV line count placement
roomote Sep 13, 2026
e20f66d
fix(ci): make WASM publication transactional
roomote Sep 13, 2026
ec7fd78
fix(ci): preserve WASMs through interruption
roomote Sep 13, 2026
3c65568
fix(ci): retain interrupted WASM recovery
roomote Sep 13, 2026
d6d9c21
test(ci): preserve recovery across retries
roomote Sep 13, 2026
4378384
test(ci): remove duplicate recovery assertion
roomote Sep 13, 2026
c493be8
fix(ci): protect WASM transaction boundaries
roomote Sep 13, 2026
79b0de8
fix(ci): preserve WASMs during cache verification
roomote Sep 13, 2026
74c2bbc
fix(ci): make WASM snapshot acquisition atomic
roomote Sep 13, 2026
dc59ffe
fix(ci): make WASM snapshot restore retryable
roomote Sep 13, 2026
edb92d0
fix(ci): separate verifier cache cleanup
roomote Sep 13, 2026
17aaaef
refactor(ci): simplify generated WASM recovery
roomote Sep 13, 2026
eaed70a
refactor(ci): isolate cached WASM test assets
roomote Sep 13, 2026
a58036a
refactor(ci): load test WASMs from dependency
roomote Sep 13, 2026
9e5c92c
test(ci): strengthen WASM dependency contract
roomote Sep 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions .github/workflows/code-qa.yml
Original file line number Diff line number Diff line change
Expand Up @@ -150,12 +150,20 @@ jobs:
restore-keys: |
${{ runner.os }}-turbo-${{ hashFiles('**/pnpm-lock.yaml') }}-
${{ runner.os }}-turbo-
- name: Run non-core coverage
run: pnpm turbo run test:coverage --filter="!@roo-code/core" --log-order grouped --output-logs new-only
- name: Run non-extension package coverage
run: pnpm turbo run test:coverage --filter="!@roo-code/core" --filter="!zoo-code" --log-order grouped --output-logs new-only
- name: Run extension unit coverage
run: pnpm turbo run test:coverage:unit --filter="zoo-code" --log-order grouped --output-logs new-only
- name: Verify extension coverage contract
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
run: pnpm turbo run test:coverage:unit --filter="@roo-code/core" --log-order grouped --output-logs new-only
- name: Run core integration coverage
run: pnpm turbo run test:coverage:integration --filter="@roo-code/core" --log-order grouped --output-logs new-only
- name: Verify extension unit coverage report
run: node src/scripts/verify-lcov.mjs src/coverage/unit/lcov.info
- name: Save Turbo cache
if: steps.turbo-cache.outputs.cache-hit != 'true'
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
Expand All @@ -177,7 +185,7 @@ jobs:
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
with:
files: >-
src/coverage/lcov.info,
src/coverage/unit/lcov.info,
packages/cloud/coverage/lcov.info,
packages/telemetry/coverage/lcov.info,
apps/cli/coverage/lcov.info
Expand Down Expand Up @@ -214,7 +222,7 @@ jobs:
with:
name: coverage-reports-${{ matrix.name }}
path: |
src/coverage/lcov.info
src/coverage/unit/lcov.info
webview-ui/coverage/lcov.info
packages/cloud/coverage/lcov.info
packages/telemetry/coverage/lcov.info
Expand Down
1 change: 1 addition & 0 deletions src/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,7 @@
"lint": "eslint . --ext=ts --max-warnings=0",
"check-types": "tsc --noEmit",
"test": "vitest run",
"verify:coverage-contract": "node scripts/verify-coverage-contract.mjs",
"test:unit": "vitest run --config vitest.unit.config.ts",
"test:dist": "vitest run --config vitest.dist.config.ts",
"test:coverage": "vitest run --coverage",
Expand Down
29 changes: 29 additions & 0 deletions src/scripts/verify-coverage-contract.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { spawnSync } from "node:child_process"
import process from "node:process"

const pnpm = process.platform === "win32" ? process.env.npm_execpath : "pnpm"

Check warning on line 4 in src/scripts/verify-coverage-contract.mjs

View workflow job for this annotation

GitHub Actions / mutation-diff

Mutation test advisory

src/scripts/verify-coverage-contract.mjs:4: 5 mutation test gaps; example: NoCoverage ConditionalExpression mutant (replacement: true). See the job summary for the complete list and resolution guidance.

Check warning on line 4 in src/scripts/verify-coverage-contract.mjs

View workflow job for this annotation

GitHub Actions / mutation-diff

Mutation test advisory

src/scripts/verify-coverage-contract.mjs:4: 5 mutation test gaps; example: NoCoverage ConditionalExpression mutant (replacement: true). See the job summary for the complete list and resolution guidance.
if (!pnpm) throw new Error("pnpm executable path is unavailable")

Check warning on line 5 in src/scripts/verify-coverage-contract.mjs

View workflow job for this annotation

GitHub Actions / mutation-diff

Mutation test advisory

src/scripts/verify-coverage-contract.mjs:5: 4 mutation test gaps; example: NoCoverage BooleanLiteral mutant (replacement: pnpm). See the job summary for the complete list and resolution guidance.

Check warning on line 5 in src/scripts/verify-coverage-contract.mjs

View workflow job for this annotation

GitHub Actions / mutation-diff

Mutation test advisory

src/scripts/verify-coverage-contract.mjs:5: 4 mutation test gaps; example: NoCoverage BooleanLiteral mutant (replacement: pnpm). See the job summary for the complete list and resolution guidance.
const command = process.platform === "win32" ? process.execPath : pnpm

Check warning on line 6 in src/scripts/verify-coverage-contract.mjs

View workflow job for this annotation

GitHub Actions / mutation-diff

Mutation test advisory

src/scripts/verify-coverage-contract.mjs:6: 4 mutation test gaps; example: NoCoverage ConditionalExpression mutant (replacement: true). See the job summary for the complete list and resolution guidance.

Check warning on line 6 in src/scripts/verify-coverage-contract.mjs

View workflow job for this annotation

GitHub Actions / mutation-diff

Mutation test advisory

src/scripts/verify-coverage-contract.mjs:6: 4 mutation test gaps; example: NoCoverage ConditionalExpression mutant (replacement: true). See the job summary for the complete list and resolution guidance.
const args = process.platform === "win32" ? [pnpm] : []

Check warning on line 7 in src/scripts/verify-coverage-contract.mjs

View workflow job for this annotation

GitHub Actions / mutation-diff

Mutation test advisory

src/scripts/verify-coverage-contract.mjs:7: 6 mutation test gaps; example: NoCoverage ConditionalExpression mutant (replacement: true). See the job summary for the complete list and resolution guidance.

Check warning on line 7 in src/scripts/verify-coverage-contract.mjs

View workflow job for this annotation

GitHub Actions / mutation-diff

Mutation test advisory

src/scripts/verify-coverage-contract.mjs:7: 6 mutation test gaps; example: NoCoverage ConditionalExpression mutant (replacement: true). See the job summary for the complete list and resolution guidance.
const result = spawnSync(
command,
[...args, "turbo", "run", "test:coverage:unit", "test:dist", "--filter=zoo-code", "--dry=json"],

Check warning on line 10 in src/scripts/verify-coverage-contract.mjs

View workflow job for this annotation

GitHub Actions / mutation-diff

Mutation test advisory

src/scripts/verify-coverage-contract.mjs:10: 7 mutation test gaps; example: NoCoverage ArrayDeclaration mutant (replacement: []). See the job summary for the complete list and resolution guidance.

Check warning on line 10 in src/scripts/verify-coverage-contract.mjs

View workflow job for this annotation

GitHub Actions / mutation-diff

Mutation test advisory

src/scripts/verify-coverage-contract.mjs:10: 7 mutation test gaps; example: NoCoverage ArrayDeclaration mutant (replacement: []). See the job summary for the complete list and resolution guidance.
{ encoding: "utf8" },

Check warning on line 11 in src/scripts/verify-coverage-contract.mjs

View workflow job for this annotation

GitHub Actions / mutation-diff

Mutation test advisory

src/scripts/verify-coverage-contract.mjs:11: 2 mutation test gaps; example: NoCoverage ObjectLiteral mutant (replacement: {}). See the job summary for the complete list and resolution guidance.

Check warning on line 11 in src/scripts/verify-coverage-contract.mjs

View workflow job for this annotation

GitHub Actions / mutation-diff

Mutation test advisory

src/scripts/verify-coverage-contract.mjs:11: 2 mutation test gaps; example: NoCoverage ObjectLiteral mutant (replacement: {}). See the job summary for the complete list and resolution guidance.
)
if (result.status !== 0) {

Check warning on line 13 in src/scripts/verify-coverage-contract.mjs

View workflow job for this annotation

GitHub Actions / mutation-diff

Mutation test advisory

src/scripts/verify-coverage-contract.mjs:13: 3 mutation test gaps; example: NoCoverage ConditionalExpression mutant (replacement: true). See the job summary for the complete list and resolution guidance.

Check warning on line 13 in src/scripts/verify-coverage-contract.mjs

View workflow job for this annotation

GitHub Actions / mutation-diff

Mutation test advisory

src/scripts/verify-coverage-contract.mjs:13: 3 mutation test gaps; example: NoCoverage ConditionalExpression mutant (replacement: true). See the job summary for the complete list and resolution guidance.
const details = [result.error?.message, result.signal, result.stderr, result.stdout].filter(Boolean).join("\n")
throw new Error(details || `pnpm exited with status ${result.status ?? "unknown"}`)
}

const graph = JSON.parse(result.stdout)
const coverageTask = graph.tasks.find(({ taskId }) => taskId === "zoo-code#test:coverage:unit")
const distTask = graph.tasks.find(({ taskId }) => taskId === "zoo-code#test:dist")
if (!coverageTask) throw new Error("Unit coverage task missing")
if (graph.tasks.some(({ taskId }) => taskId === "zoo-code#prepare:tree-sitter-wasms"))
throw new Error("Removed WASM preparation task remains in the graph")
if (coverageTask.dependencies.includes("zoo-code#bundle")) throw new Error("Unit coverage must not depend on bundle")
if (!coverageTask.dependencies.includes("@roo-code/types#build"))
throw new Error("Unit coverage must depend on the types build")
if (!Object.hasOwn(coverageTask.inputs, "package.json")) throw new Error("Unit coverage must hash package.json")
Comment thread
zoomote[bot] marked this conversation as resolved.
if (!coverageTask.hashOfExternalDependencies) throw new Error("Unit coverage must hash external dependencies")
if (!distTask?.dependencies.includes("zoo-code#bundle")) throw new Error("Dist smoke test must depend on bundle")
46 changes: 46 additions & 0 deletions src/scripts/verify-lcov.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import fs from "node:fs"
import process from "node:process"
import { fileURLToPath } from "node:url"

export function verifyLcov(content) {
let inRecord = false
let anyCovered = false
let linesFound
let linesHit

for (const line of content.split(/\r?\n/)) {
if (line.startsWith("SF:")) {
Comment thread
zoomote[bot] marked this conversation as resolved.
if (inRecord) throw new Error("LCOV source record is not terminated")

Check warning on line 13 in src/scripts/verify-lcov.mjs

View workflow job for this annotation

GitHub Actions / mutation-diff

Mutation test advisory

src/scripts/verify-lcov.mjs:13: Survived StringLiteral mutant (replacement: ""). See the job summary for the complete list and resolution guidance.

Check warning on line 13 in src/scripts/verify-lcov.mjs

View workflow job for this annotation

GitHub Actions / mutation-diff

Mutation test advisory

src/scripts/verify-lcov.mjs:13: Survived StringLiteral mutant (replacement: ""). See the job summary for the complete list and resolution guidance.
if (!line.slice(3)) throw new Error("LCOV source path is empty")

Check warning on line 14 in src/scripts/verify-lcov.mjs

View workflow job for this annotation

GitHub Actions / mutation-diff

Mutation test advisory

src/scripts/verify-lcov.mjs:14: Survived StringLiteral mutant (replacement: ""). See the job summary for the complete list and resolution guidance.

Check warning on line 14 in src/scripts/verify-lcov.mjs

View workflow job for this annotation

GitHub Actions / mutation-diff

Mutation test advisory

src/scripts/verify-lcov.mjs:14: Survived StringLiteral mutant (replacement: ""). See the job summary for the complete list and resolution guidance.
inRecord = true
linesFound = undefined
linesHit = undefined
} else if (line.startsWith("LF:")) {
if (!inRecord) throw new Error("LCOV line count is outside a source record")

Check warning on line 19 in src/scripts/verify-lcov.mjs

View workflow job for this annotation

GitHub Actions / mutation-diff

Mutation test advisory

src/scripts/verify-lcov.mjs:19: 2 mutation test gaps; example: Survived ConditionalExpression mutant (replacement: false). See the job summary for the complete list and resolution guidance.

Check warning on line 19 in src/scripts/verify-lcov.mjs

View workflow job for this annotation

GitHub Actions / mutation-diff

Mutation test advisory

src/scripts/verify-lcov.mjs:19: 2 mutation test gaps; example: Survived ConditionalExpression mutant (replacement: false). See the job summary for the complete list and resolution guidance.
if (linesFound !== undefined) throw new Error("LCOV source record has duplicate line counts")
const found = line.slice(3)
if (!/^\d+$/.test(found)) throw new Error("LCOV line count is not a decimal integer")
linesFound = BigInt(found)
Comment thread
zoomote[bot] marked this conversation as resolved.
} else if (line.startsWith("LH:")) {
if (!inRecord) throw new Error("LCOV hit count is outside a source record")
if (linesHit !== undefined) throw new Error("LCOV source record has duplicate hit counts")
const hits = line.slice(3)
if (!/^\d+$/.test(hits)) throw new Error("LCOV hit count is not a decimal integer")
linesHit = BigInt(hits)
} else if (line === "end_of_record") {
if (!inRecord) throw new Error("LCOV terminator is outside a source record")
if (linesFound === undefined || linesHit === undefined)
throw new Error("LCOV source record has incomplete line summaries")
if (linesHit > linesFound) throw new Error("LCOV hit count exceeds lines found")
if (linesHit > 0n) anyCovered = true
inRecord = false
}
}

if (inRecord) throw new Error("LCOV source record is not terminated")
if (!anyCovered) throw new Error("LCOV report has no covered lines")
}

if (process.argv[1] === fileURLToPath(import.meta.url)) {
verifyLcov(fs.readFileSync(process.argv[2], "utf8"))
}
31 changes: 31 additions & 0 deletions src/scripts/verify-lcov.spec.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { describe, expect, it } from "vitest"

import { verifyLcov } from "./verify-lcov.mjs"

describe("verifyLcov", () => {
it("accepts complete records with covered lines", () => {
expect(() => verifyLcov("SF:file.ts\nLF:1\nLH:1\nend_of_record\n")).not.toThrow()
})

it.each([
["an empty source path", "SF:\nLF:1\nLH:1\nend_of_record\n"],
["an unterminated record", "SF:file.ts\nLH:1\n"],
["a zero-hit report", "SF:file.ts\nLF:1\nLH:0\nend_of_record\n"],
["a line count outside a record", "LF:1\n"],
["a hit count outside a record", "LH:1\n"],
["a terminator outside a record", "end_of_record\n"],
["consecutive source records", "SF:first.ts\nSF:second.ts\nLF:1\nLH:1\nend_of_record\n"],
["a record without lines found", "SF:file.ts\nLH:1\nend_of_record\n"],
["an infinite line count", "SF:file.ts\nLF:Infinity\nLH:1\nend_of_record\n"],
["a fractional line count", "SF:file.ts\nLF:1.5\nLH:1\nend_of_record\n"],
["an exponential line count", "SF:file.ts\nLF:1e3\nLH:1\nend_of_record\n"],
["an infinite hit count", "SF:file.ts\nLH:Infinity\nend_of_record\n"],
["a fractional hit count", "SF:file.ts\nLH:1.5\nend_of_record\n"],
["an exponential hit count", "SF:file.ts\nLH:1e3\nend_of_record\n"],
["duplicate line counts", "SF:file.ts\nLF:1\nLF:0\nLH:0\nend_of_record\n"],
["duplicate hit counts", "SF:file.ts\nLF:1\nLH:1\nLH:0\nend_of_record\n"],
["more hit lines than found lines", "SF:file.ts\nLF:0\nLH:1\nend_of_record\n"],
])("rejects %s", (_, content) => {
expect(() => verifyLcov(content)).toThrow()
})
})
18 changes: 4 additions & 14 deletions src/services/tree-sitter/__tests__/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import * as path from "path"
import tsxQuery from "../queries/tsx"
import { Parser, Language } from "web-tree-sitter"

import { loadTestGrammar } from "./wasm"

vi.mock("fs/promises")
export const mockedFs = vi.mocked(fs)

Expand Down Expand Up @@ -34,16 +36,6 @@ export async function initializeTreeSitter() {
// Initialize directly using the default export or the module itself
await Parser.init()

// Override the Parser.Language.load to use dist directory
const originalLoad = Language.load

Language.load = async (wasmPath: string) => {
const filename = path.basename(wasmPath)
const correctPath = path.join(process.cwd(), "dist", filename)
// console.log(`Redirecting WASM load from ${wasmPath} to ${correctPath}`)
return originalLoad(correctPath)
}

initializedTreeSitter = { Parser, Language }
}

Expand Down Expand Up @@ -84,8 +76,7 @@ export async function testParseSourceCodeDefinitions(
const parser = new Parser()

// Load language and configure parser
const wasmPath = path.join(process.cwd(), `dist/${wasmFile}`)
const lang = await Language.load(wasmPath)
const lang = await loadTestGrammar(path.basename(wasmFile))
parser.setLanguage(lang)

// Create a real query
Expand Down Expand Up @@ -113,8 +104,7 @@ export async function testParseSourceCodeDefinitions(
export async function inspectTreeStructure(content: string, language: string = "typescript"): Promise<string> {
const { Parser, Language } = await initializeTreeSitter()
const parser = new Parser()
const wasmPath = path.join(process.cwd(), `dist/tree-sitter-${language}.wasm`)
const lang = await Language.load(wasmPath)
const lang = await loadTestGrammar(`tree-sitter-${language}.wasm`)
parser.setLanguage(lang)

// Parse the content
Expand Down
87 changes: 87 additions & 0 deletions src/services/tree-sitter/__tests__/wasm.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import fs from "fs"
import os from "os"
import path from "path"
import { afterEach, beforeAll, describe, expect, it } from "vitest"
import { Parser } from "web-tree-sitter"

import { loadTestGrammar } from "./wasm"

const requiredGrammars = [
"c",
"cpp",
"c_sharp",
"css",
"dart",
"elisp",
"elixir",
"embedded_template",
"go",
"html",
"java",
"javascript",
"json",
"kotlin",
"lua",
"ocaml",
"php",
"python",
"ruby",
"rust",
"scala",
"solidity",
"swift",
"systemrdl",
"tlaplus",
"toml",
"tsx",
"typescript",
"vue",
"zig",
]

async function captureLoadFailure(filename: string, directory: string) {
let error: unknown
try {
await loadTestGrammar(filename, directory)
} catch (caught) {
error = caught
}
if (!(error instanceof Error) || !(error.cause instanceof Error)) {
throw new Error("Expected a contextual grammar load error with an Error cause")
}
expect(error.message).toContain(error.cause.message)
return error
}

describe("dependency-owned Tree-sitter grammars", () => {
const temporaryDirectories: string[] = []

beforeAll(() => Parser.init())
afterEach(() => temporaryDirectories.splice(0).forEach((directory) => fs.rmSync(directory, { recursive: true })))

it.each(requiredGrammars)("loads tree-sitter-%s.wasm", async (grammar) => {
const language = await loadTestGrammar(`tree-sitter-${grammar}.wasm`)
expect(() => new Parser().setLanguage(language)).not.toThrow()
})

it("reports a missing dependency artifact with its filename and resolved path", async () => {
const directory = fs.mkdtempSync(path.join(os.tmpdir(), "tree-sitter-wasm-missing-"))
temporaryDirectories.push(directory)

const error = await captureLoadFailure("tree-sitter-missing.wasm", directory)
expect(error.message).toContain(
`Failed to load Tree-sitter grammar tree-sitter-missing.wasm from ${path.join(directory, "tree-sitter-missing.wasm")}`,
)
})

it("reports a malformed dependency artifact with its filename and resolved path", async () => {
const directory = fs.mkdtempSync(path.join(os.tmpdir(), "tree-sitter-wasm-malformed-"))
temporaryDirectories.push(directory)
fs.writeFileSync(path.join(directory, "tree-sitter-malformed.wasm"), "not wasm")

const error = await captureLoadFailure("tree-sitter-malformed.wasm", directory)
expect(error.message).toContain(
`Failed to load Tree-sitter grammar tree-sitter-malformed.wasm from ${path.join(directory, "tree-sitter-malformed.wasm")}`,
)
})
})
14 changes: 14 additions & 0 deletions src/services/tree-sitter/__tests__/wasm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import path from "path"
import { Language } from "web-tree-sitter"

export const TEST_WASM_DIR = path.join(__dirname, "../../../node_modules/tree-sitter-wasms/out")

export async function loadTestGrammar(filename: string, directory = TEST_WASM_DIR) {
const wasmPath = path.join(directory, filename)
try {
return await Language.load(wasmPath)
} catch (error) {
const detail = error instanceof Error ? error.message : String(error)
throw new Error(`Failed to load Tree-sitter grammar ${filename} from ${wasmPath}: ${detail}`, { cause: error })
}
}
Loading