Files
everything-claude-code/.opencode/tools/check-coverage.ts
Affaan Mustafa 6c2e0eace8 fix: update opencode-ecc plugin for SDK v1.1.53 and refresh README stats
- Fix PluginContext → PluginInput type rename in @opencode-ai/plugin
- Import tool from @opencode-ai/plugin/tool subpath (fixes broken barrel export)
- Update client.app.log() calls to use new options-object API signature
- Stringify tool execute return values (SDK now requires Promise<string>)
- Add .js extensions to relative imports for NodeNext module resolution
- Update README star count (42K+) and contributor count (24)
2026-02-09 01:32:22 -08:00

171 lines
4.7 KiB
TypeScript

/**
* Check Coverage Tool
*
* Custom OpenCode tool to analyze test coverage and report on gaps.
* Supports common coverage report formats.
*/
import { tool } from "@opencode-ai/plugin/tool"
import * as path from "path"
import * as fs from "fs"
export default tool({
description:
"Check test coverage against a threshold and identify files with low coverage. Reads coverage reports from common locations.",
args: {
threshold: tool.schema
.number()
.optional()
.describe("Minimum coverage percentage required (default: 80)"),
showUncovered: tool.schema
.boolean()
.optional()
.describe("Show list of uncovered files (default: true)"),
format: tool.schema
.enum(["summary", "detailed", "json"])
.optional()
.describe("Output format (default: summary)"),
},
async execute(args, context) {
const threshold = args.threshold ?? 80
const showUncovered = args.showUncovered ?? true
const format = args.format ?? "summary"
const cwd = context.worktree || context.directory
// Look for coverage reports
const coveragePaths = [
"coverage/coverage-summary.json",
"coverage/lcov-report/index.html",
"coverage/coverage-final.json",
".nyc_output/coverage.json",
]
let coverageData: CoverageSummary | null = null
let coverageFile: string | null = null
for (const coveragePath of coveragePaths) {
const fullPath = path.join(cwd, coveragePath)
if (fs.existsSync(fullPath) && coveragePath.endsWith(".json")) {
try {
const content = JSON.parse(fs.readFileSync(fullPath, "utf-8"))
coverageData = parseCoverageData(content)
coverageFile = coveragePath
break
} catch {
// Continue to next file
}
}
}
if (!coverageData) {
return JSON.stringify({
success: false,
error: "No coverage report found",
suggestion:
"Run tests with coverage first: npm test -- --coverage",
searchedPaths: coveragePaths,
})
}
const passed = coverageData.total.percentage >= threshold
const uncoveredFiles = coverageData.files.filter(
(f) => f.percentage < threshold
)
const result: CoverageResult = {
success: passed,
threshold,
coverageFile,
total: coverageData.total,
passed,
}
if (format === "detailed" || (showUncovered && uncoveredFiles.length > 0)) {
result.uncoveredFiles = uncoveredFiles.slice(0, 20) // Limit to 20 files
result.uncoveredCount = uncoveredFiles.length
}
if (format === "json") {
result.rawData = coverageData
}
if (!passed) {
result.suggestion = `Coverage is ${coverageData.total.percentage.toFixed(1)}% which is below the ${threshold}% threshold. Focus on these files:\n${uncoveredFiles
.slice(0, 5)
.map((f) => `- ${f.file}: ${f.percentage.toFixed(1)}%`)
.join("\n")}`
}
return JSON.stringify(result)
},
})
interface CoverageSummary {
total: {
lines: number
covered: number
percentage: number
}
files: Array<{
file: string
lines: number
covered: number
percentage: number
}>
}
interface CoverageResult {
success: boolean
threshold: number
coverageFile: string | null
total: CoverageSummary["total"]
passed: boolean
uncoveredFiles?: CoverageSummary["files"]
uncoveredCount?: number
rawData?: CoverageSummary
suggestion?: string
}
function parseCoverageData(data: unknown): CoverageSummary {
// Handle istanbul/nyc format
if (typeof data === "object" && data !== null && "total" in data) {
const istanbulData = data as Record<string, unknown>
const total = istanbulData.total as Record<string, { total: number; covered: number }>
const files: CoverageSummary["files"] = []
for (const [key, value] of Object.entries(istanbulData)) {
if (key !== "total" && typeof value === "object" && value !== null) {
const fileData = value as Record<string, { total: number; covered: number }>
if (fileData.lines) {
files.push({
file: key,
lines: fileData.lines.total,
covered: fileData.lines.covered,
percentage: fileData.lines.total > 0
? (fileData.lines.covered / fileData.lines.total) * 100
: 100,
})
}
}
}
return {
total: {
lines: total.lines?.total || 0,
covered: total.lines?.covered || 0,
percentage: total.lines?.total
? (total.lines.covered / total.lines.total) * 100
: 0,
},
files,
}
}
// Default empty result
return {
total: { lines: 0, covered: 0, percentage: 0 },
files: [],
}
}