Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 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
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 .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ mock/
# Builds
bin/
*.vsix
/src/generated/

# Local prompts and rules
/local-prompts
Expand Down
2 changes: 2 additions & 0 deletions src/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,8 @@
"lint": "eslint . --ext=ts --max-warnings=0",
"check-types": "tsc --noEmit",
"test": "vitest run",
"prepare:tree-sitter-wasms": "node scripts/copy-tree-sitter-wasms.mjs",
"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
30 changes: 30 additions & 0 deletions src/scripts/copy-tree-sitter-wasms.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import fs from "node:fs"
import path from "node:path"
import process from "node:process"
import { fileURLToPath } from "node:url"

const srcDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..")

Check warning on line 6 in src/scripts/copy-tree-sitter-wasms.mjs

View workflow job for this annotation

GitHub Actions / mutation-diff

Mutation test advisory

src/scripts/copy-tree-sitter-wasms.mjs:6: Survived StringLiteral mutant (replacement: ""). See the job summary for the complete list and resolution guidance.
const wasmDir = path.join(srcDir, "node_modules", "tree-sitter-wasms", "out")

Check warning on line 7 in src/scripts/copy-tree-sitter-wasms.mjs

View workflow job for this annotation

GitHub Actions / mutation-diff

Mutation test advisory

src/scripts/copy-tree-sitter-wasms.mjs:7: 3 mutation test gaps; example: Survived StringLiteral mutant (replacement: ""). See the job summary for the complete list and resolution guidance.
const generatedDir = path.join(srcDir, "generated", "tree-sitter-wasms")

Check warning on line 8 in src/scripts/copy-tree-sitter-wasms.mjs

View workflow job for this annotation

GitHub Actions / mutation-diff

Mutation test advisory

src/scripts/copy-tree-sitter-wasms.mjs:8: 2 mutation test gaps; example: Survived StringLiteral mutant (replacement: ""). See the job summary for the complete list and resolution guidance.
const wasmPattern = /^tree-sitter-.*\.wasm$/

Check warning on line 9 in src/scripts/copy-tree-sitter-wasms.mjs

View workflow job for this annotation

GitHub Actions / mutation-diff

Mutation test advisory

src/scripts/copy-tree-sitter-wasms.mjs:9: 3 mutation test gaps; example: Survived Regex mutant (replacement: /tree-sitter-.*\.wasm$/). See the job summary for the complete list and resolution guidance.

export async function prepareTreeSitterWasms(sourceDir, destinationDir, { filesystem = fs.promises } = {}) {
const sourceFiles = (await filesystem.readdir(sourceDir)).filter((filename) => wasmPattern.test(filename)).sort()

Check warning on line 12 in src/scripts/copy-tree-sitter-wasms.mjs

View workflow job for this annotation

GitHub Actions / mutation-diff

Mutation test advisory

src/scripts/copy-tree-sitter-wasms.mjs:12: Survived MethodExpression mutant (replacement: (await filesystem.readdir(sourceDir)).filter(filename => wasmPattern.test(filename))). See the job summary for the complete list and resolution guidance.
Comment thread
zoomote[bot] marked this conversation as resolved.
if (sourceFiles.length === 0) throw new Error("WASM source set is empty")

Check warning on line 13 in src/scripts/copy-tree-sitter-wasms.mjs

View workflow job for this annotation

GitHub Actions / mutation-diff

Mutation test advisory

src/scripts/copy-tree-sitter-wasms.mjs:13: 2 mutation test gaps; example: NoCoverage StringLiteral mutant (replacement: ""). See the job summary for the complete list and resolution guidance.

await filesystem.rm(destinationDir, { recursive: true, force: true })
await filesystem.mkdir(destinationDir, { recursive: true })
try {
for (const filename of sourceFiles) {
await filesystem.copyFile(path.join(sourceDir, filename), path.join(destinationDir, filename))
}
} catch (error) {
await filesystem.rm(destinationDir, { recursive: true, force: true })

Check warning on line 22 in src/scripts/copy-tree-sitter-wasms.mjs

View workflow job for this annotation

GitHub Actions / mutation-diff

Mutation test advisory

src/scripts/copy-tree-sitter-wasms.mjs:22: Survived BooleanLiteral mutant (replacement: false). See the job summary for the complete list and resolution guidance.
throw error
}
return { sourceFiles }
}

if (process.argv[1] === fileURLToPath(import.meta.url)) {
await prepareTreeSitterWasms(wasmDir, generatedDir)
}
57 changes: 57 additions & 0 deletions src/scripts/copy-tree-sitter-wasms.spec.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import fs from "node:fs"
import os from "node:os"
import path from "node:path"
import { afterEach, beforeEach, describe, expect, it } from "vitest"

import { prepareTreeSitterWasms } from "./copy-tree-sitter-wasms.mjs"

describe("prepareTreeSitterWasms", () => {
let root
let source
let destination

beforeEach(() => {
root = fs.mkdtempSync(path.join(os.tmpdir(), "tree-sitter-wasms-"))
source = path.join(root, "source")
destination = path.join(root, "generated", "tree-sitter-wasms")
fs.mkdirSync(source)
fs.writeFileSync(path.join(source, "tree-sitter-a.wasm"), "a")
})

afterEach(() => fs.rmSync(root, { recursive: true, force: true }))

it("replaces pre-existing generated output without filesystem rename", async () => {
fs.writeFileSync(path.join(source, "ignored.txt"), "ignored")
fs.mkdirSync(destination, { recursive: true })
fs.writeFileSync(path.join(destination, "tree-sitter-stale.wasm"), "stale")
const filesystem = {
...fs.promises,
rename: async () => {
throw new Error("rename must not be used")
},
}

await prepareTreeSitterWasms(source, destination, { filesystem })

expect(fs.readdirSync(destination)).toEqual(["tree-sitter-a.wasm"])
expect(fs.readFileSync(path.join(destination, "tree-sitter-a.wasm"), "utf8")).toBe("a")
})

it("removes partial task output after a copy failure and rebuilds cleanly", async () => {
fs.writeFileSync(path.join(source, "tree-sitter-b.wasm"), "b")
let copies = 0
const filesystem = {
...fs.promises,
copyFile: async (...args) => {
if (++copies === 2) throw new Error("copy failed")
return fs.promises.copyFile(...args)
},
}

await expect(prepareTreeSitterWasms(source, destination, { filesystem })).rejects.toThrow("copy failed")
expect(fs.existsSync(destination)).toBe(false)

await prepareTreeSitterWasms(source, destination)
expect(fs.readdirSync(destination)).toEqual(["tree-sitter-a.wasm", "tree-sitter-b.wasm"])
})
})
98 changes: 98 additions & 0 deletions src/scripts/verify-coverage-contract.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { spawnSync } from "node:child_process"
import fs from "node:fs"
import path from "node:path"
import process from "node:process"
import { fileURLToPath } from "node:url"

import { assertMatchingFiles } from "./verify-wasm-files.mjs"

const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..")

Check warning on line 9 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:9: NoCoverage StringLiteral mutant (replacement: ""). See the job summary for the complete list and resolution guidance.
const pnpm = process.platform === "win32" ? process.env.npm_execpath : "pnpm"

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: 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 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: 4 mutation test gaps; example: NoCoverage BooleanLiteral mutant (replacement: pnpm). See the job summary for the complete list and resolution guidance.
const pnpmPrefix = process.platform === "win32" ? [pnpm] : []
const run = (args, options = {}) => {
const { includeStderr = true, ...spawnOptions } = options
const command = process.platform === "win32" ? process.execPath : pnpm
const result = spawnSync(command, [...pnpmPrefix, ...args], { cwd: root, encoding: "utf8", ...spawnOptions })
if (result.status !== 0) {
const details = [
result.error?.message,
result.signal ? `terminated by ${result.signal}` : undefined,
result.stderr,
result.stdout,
]
.filter(Boolean)
.join("\n")
throw new Error(details || `pnpm exited with status ${result.status ?? "unknown"}`)
}
return `${result.stdout || ""}${includeStderr ? result.stderr || "" : ""}`
}

const graph = JSON.parse(
run(["turbo", "run", "test:coverage:unit", "--filter=zoo-code", "--dry=json"], { includeStderr: false }),
)
const coverageTask = graph.tasks.find(({ taskId }) => taskId === "zoo-code#test:coverage:unit")
const preparationTask = graph.tasks.find(({ taskId }) => taskId === "zoo-code#prepare:tree-sitter-wasms")
if (!coverageTask?.dependencies.includes("zoo-code#prepare:tree-sitter-wasms"))
throw new Error("WASM prerequisite missing")
if (coverageTask.dependencies.includes("zoo-code#bundle")) throw new Error("Unit coverage must not depend on bundle")
if (JSON.stringify(preparationTask?.outputs) !== JSON.stringify(["generated/tree-sitter-wasms/**"]))
throw new Error("WASM prerequisite outputs changed")

const generated = path.join(root, "src", "generated", "tree-sitter-wasms")
const cacheDir = path.join(root, ".turbo", "coverage-contract")
fs.rmSync(cacheDir, { recursive: true, force: true })

try {
run(["turbo", "run", "prepare:tree-sitter-wasms", "--filter=zoo-code", "--cache-dir=.turbo/coverage-contract"])
run(["--dir", "src", "exec", "vitest", "run", "services/tree-sitter/__tests__"], { stdio: "inherit" })

const source = fs
.readdirSync(path.join(root, "src", "node_modules", "tree-sitter-wasms", "out"))
.filter((filename) => /^tree-sitter-.*\.wasm$/.test(filename))
.sort()
const prepared = fs
.readdirSync(generated)
.filter((filename) => /^tree-sitter-.*\.wasm$/.test(filename))
.sort()
if (source.length === 0) throw new Error("Dependency contains no tree-sitter WASMs")
if (JSON.stringify(source) !== JSON.stringify(prepared))
throw new Error("Prepared WASM set does not match dependency")
assertMatchingFiles(
path.join(root, "src", "node_modules", "tree-sitter-wasms", "out"),
generated,
source,
"Prepared WASM content does not match dependency",
)

fs.rmSync(generated, { recursive: true, force: true })
const warmGraph = JSON.parse(
run(
[
"turbo",
"run",
"prepare:tree-sitter-wasms",
"--filter=zoo-code",
"--cache-dir=.turbo/coverage-contract",
"--dry=json",
],
{ includeStderr: false },
),
)
const warmTask = warmGraph.tasks.find(({ taskId }) => taskId === "zoo-code#prepare:tree-sitter-wasms")
if (warmTask?.cache.status !== "HIT") throw new Error("WASM prerequisite is not available in the isolated cache")
run(["turbo", "run", "prepare:tree-sitter-wasms", "--filter=zoo-code", "--cache-dir=.turbo/coverage-contract"])
const restored = fs
.readdirSync(generated)
.filter((filename) => /^tree-sitter-.*\.wasm$/.test(filename))
.sort()
if (JSON.stringify(source) !== JSON.stringify(restored)) throw new Error("WASM cache did not restore exact outputs")
assertMatchingFiles(
path.join(root, "src", "node_modules", "tree-sitter-wasms", "out"),
generated,
source,
"WASM cache restored corrupted output",
)
} finally {
fs.rmSync(cacheDir, { recursive: true, force: true })
}
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")
if (!line.slice(3)) throw new Error("LCOV source path is empty")
inRecord = true
linesFound = undefined
linesHit = undefined
} else if (line.startsWith("LF:")) {
if (!inRecord) throw new Error("LCOV line count is outside a source record")
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()
})
})
14 changes: 14 additions & 0 deletions src/scripts/verify-wasm-files.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import fs from "node:fs"
import path from "node:path"

export function assertMatchingFiles(expectedDir, actualDir, filenames, message, filesystem = fs) {
for (const filename of filenames) {
if (
!filesystem
.readFileSync(path.join(expectedDir, filename))
.equals(filesystem.readFileSync(path.join(actualDir, filename)))
) {
throw new Error(`${message}: ${filename}`)
}
}
}
29 changes: 29 additions & 0 deletions src/scripts/verify-wasm-files.spec.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import fs from "node:fs"
import os from "node:os"
import path from "node:path"
import { afterEach, beforeEach, describe, expect, it } from "vitest"

import { assertMatchingFiles } from "./verify-wasm-files.mjs"

describe("assertMatchingFiles", () => {
let root

beforeEach(() => {
root = fs.mkdtempSync(path.join(os.tmpdir(), "verify-wasm-files-"))
})

afterEach(() => fs.rmSync(root, { recursive: true, force: true }))

it("rejects a restored WASM with corrupted content", () => {
const expected = path.join(root, "expected")
const actual = path.join(root, "actual")
fs.mkdirSync(expected)
fs.mkdirSync(actual)
fs.writeFileSync(path.join(expected, "tree-sitter-a.wasm"), "expected")
fs.writeFileSync(path.join(actual, "tree-sitter-a.wasm"), "corrupted")

expect(() => assertMatchingFiles(expected, actual, ["tree-sitter-a.wasm"], "WASM mismatch")).toThrow(
"WASM mismatch: tree-sitter-a.wasm",
)
})
})
Loading
Loading