From vibe-test
This skill should be used when the user says `/vibe-test:coverage`. Standalone honest-denominator coverage measurement with builder-facing adaptation-prompt UX. Detects the test framework, proposes the diff to add `--coverage.all` (vitest) or `--collectCoverageFrom` glob (jest); only applies on builder opt-in. Falls back to `c8 --all` when adaptation is refused. Defers raw coverage parsing to `tessl:analyzing-test-coverage` when present. Emits a CI-friendly JSON sidecar at `.vibe-test/state/coverage.json` for machine consumers; exits 0 regardless of threshold (gate decides pass/fail).
How this skill is triggered — by the user, by Claude, or both
Slash command
/vibe-test:coverageThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Read [`../guide/SKILL.md`](../guide/SKILL.md) for your overall behavior (persona, experience-level adaptation, Pattern #13 composition rules, version resolution). Then follow this command end to end.
Read ../guide/SKILL.md for your overall behavior (persona, experience-level adaptation, Pattern #13 composition rules, version resolution). Then follow this command end to end.
Coverage is the standalone measurement surface. A builder who runs /vibe-test:coverage is asking "what does this repo actually cover, honestly?" The command's flagship claim — honesty — is operationalized two ways: (1) the denominator matches the actual source tree, not just files the tests happened to import; (2) the builder, not the SKILL, chooses whether to mutate their test command. The SKILL proposes the diff, explains the cost, and applies only on opt-in.
Detect framework → propose adaptation diff → apply on opt-in or fall back to c8 --all → run coverage → compute weighted score per level → emit three views (markdown / banner / JSON sidecar) → exit 0 regardless of threshold (gate decides pass/fail).
package.json with a test runner in dependencies / devDependencies (vitest, jest, mocha) OR a framework config file (vitest.config.*, jest.config.*). If none:
"I don't see vitest, jest, or mocha in this repo. Coverage needs a test runner to measure against. Want to install one, or point me at a subdirectory that has one?"
Wait for the builder's decision. Never measure "coverage" without a runner.
--path <glob> is passed, narrow the source-file denominator to files matching the glob. Scoped coverage writes to .vibe-test/state/coverage-<scope-hash>.json to avoid clobbering full-repo state.audit.json (or audit-<hash>.json) exists, use its inventory.scanned_files as the denominator's source-of-truth. Without prior audit, scan live via src/scanner/scan() — takes 1-3s on most repos..vibe-test/state.json for a cached decline; if present, skip the proposal and go straight to c8 --all fallback.../guide/SKILL.md. Persona opening/handoff lines, experience-level verbosity, Pattern #13 anchored + dynamic, session memory interfaces.../guide/references/data-contracts.md section "coverage-state". You own writes to <repo>/.vibe-test/state/coverage.json (and scoped variant) + its history copy. You read audit.json for source-file list when present.../guide/references/plays-well-with.md. Entries with applies_to: coverage are tessl:analyzing-test-coverage (raw parsing deferral).../guide/references/friction-triggers.md /vibe-test:coverage section.src/reporter/tier-adaptive-language.ts.../session-logger/SKILL.md. start('coverage', project) at entry, end({sessionUUID, command: 'coverage', outcome}) at exit.Import surface (all existing src/ modules — no new TypeScript introduced):
src/coverage/runCoverage — orchestrates adapter proposal → adapter apply or c8 fallback → denominator-honesty check.src/coverage/computeWeightedScore / TIER_THRESHOLDS / LEVEL_WEIGHTS — the locked formula.src/coverage/checkDenominator — cherry-picked denominator detection.src/coverage/proposeVitestCoverageAll / proposeJestCollectCoverageFrom — framework-specific proposals (exposed for direct use when coverage needs to show the diff separately from running).src/scanner/scan — for live-scanning when no cached inventory is present.src/scanner/framework-detector — detect the active test framework.src/state/project-state — audit-state reads, sidecar path resolution.src/state/session-log / beacons — instrumentation.src/reporter — createReportObject, three renderers, getLanguageKnobs.Before any user-facing output:
shared.preferences.persona, shared.preferences.pacing, and plugins.vibe-test.testing_experience (fallback shared.technical_experience.level) from ~/.claude/profiles/builder.json.session-logger.start('coverage', project_basename). Hold the returned sessionUUID in memory until Step 8.Run the blocking prereq described above. If the check fails, render a gentle block and halt. Do NOT proceed; do invoke session-logger.end({outcome: 'aborted'}).
Parse ../guide/references/plays-well-with.md via loadAnchoredRegistry(). Filter to entries whose applies_to includes coverage.
tessl:analyzing-test-coverage — announce verbatim when present: "Tessl's coverage skill owns raw coverage parsing — I'll defer the numbers to it and overlay tier-appropriate interpretation on top. You'll see the same per-level breakdown either way." In practice, when Tessl is present: Vibe Test still runs the adapter / c8 fallback (we need the denominator-honesty check), but parsing the JSON reporter output is deferred — Tessl's output becomes the per_level source rather than our parseC8Json() fallback.Surface at most ONE anchored complement announcement per invocation. Dynamic discovery is capped at one additional suggestion per the guide's heuristic table.
inventory if present; otherwise invoke scan(repoRoot, scopeGlob) to build a fresh one (takes 1-3s).inventory.detection.test, pick the most-specific framework (vitest > jest > mocha > c8-standalone).package.json → scripts["test:coverage"]package.json → scripts["coverage"]package.json → scripts["test"] with --coverage already presentvitest run --coverage / jest --coverage)actualSourceFiles from inventory.scanned_files filtered to testable sources (exclude node_modules, dist, test files themselves).Call runCoverage({framework, cwd, adapterAccepted: null, actualSourceFiles, c8TestCommand}). With adapterAccepted: null, the helper returns the adapter_proposal (diff string + target file) but does not mutate anything and does not run c8 either.
Adaptation-prompt UX (C1 — the flagship):
Render the diff to the builder with tier-appropriate framing:
"Your current coverage command measures only files that tests import — that's a cherry-picked denominator. Think of it like grading a test where you only answer questions you like: the score looks great, but it's measuring a different thing than you think. Here's a one-line change that adds
--coverage.allso every source file ends up in the denominator:<diff block>Apply? [y/N]. If you skip it, I'll fall back toc8 --allfor this run — same honest denominator, just measured out-of-band."
"Coverage command is cherry-picked — denominator excludes unimported source files. Diff adds
--coverage.allfor vitest (orcollectCoverageFromglob for jest).<diff block>Apply? [y/N]."
"Adapter diff for honest denominator:
<diff block>. [y/N]. Fallback: c8 --all."
Apply path:
y → re-invoke runCoverage({..., adapterAccepted: true}). The helper applies the proposal (mutating the target file atomically) and runs the adapted command via c8TestCommand or falls through to c8 --all.n → re-invoke runCoverage({..., adapterAccepted: false}). The helper falls through to c8 --all shelling npx c8 --all --reporter json --reporter text <cmd>. Announce the extra run in the banner: "Declined adaptation — falling back to c8 --all for this run. The adapter diff is still available if you want to apply it later."friction_type: "coverage_adapter_refused" at confidence medium when builder declines. Cache the decline in .vibe-test/state.json.coverage_adapter_declined = true so future runs skip the prompt silently (builder can clear by removing the key).If coverage fails outright (child process crashes, command missing): attach a harness-break finding with severity: critical to the ReportObject and continue with a zeroed score — better to report honestly than fake numbers. Point the builder at /vibe-test:fix for the repair.
coverage.denominator from runCoverage() already carries is_cherry_picked. When true (ratio < 0.75 threshold):
category: 'cherry-picked-denominator', severity: 'high', rationale naming reported-vs-actual count + list of missing_files (truncated to 10 for the banner).example_pattern shows the minimal-diff fix tailored to framework.If is_cherry_picked === false but the builder declined adaptation earlier: note this in the banner as "Denominator honest via c8 --all fallback — no config change applied.". The adapter diff is stashed in the JSON sidecar for future reference.
tessl:analyzing-test-coverage is present AND the builder authorized deferral → call into Tessl and use its per-level result.smoke + behavioral in equal split (finer attribution deferred to v0.3).applicability per test level from the classification matrix for the audit's app_type — if no audit exists, default to {smoke: true, behavioral: true, edge: true, integration: false, performance: false}.computeWeightedScore({perLevel, applicability, tier}). Use audit.classification.tier when available; otherwise 'public-facing' as a conservative default.WeightedScoreResult (score + threshold + pass flag + contributions) to the ReportObject's score field.The score.per_level rendered in the banner shows each level's raw coverage %, not the weighted contribution — the weighted value lives in the overall score.
Build the ReportObject via createReportObject({command: 'coverage', plugin_version, repo_root, scope, commit_hash}). Populate:
classification — carry forward from audit-state if present; otherwise synthesize a minimal {app_type: 'spa' | ..., tier: 'public-facing', modifiers: [], confidence: 0.5} placeholder. The banner renders "classification pulled from audit" vs "default" in the dim section.score — from Step 6. Includes current + target + per-level.findings — cherry-picked-denominator finding if detected; harness-break finding if coverage failed to run.actions_taken — record what actually happened: {kind: 'write', description: 'applied vitest coverage-all adapter', target: 'vitest.config.ts'} OR {kind: 'other', description: 'fell back to c8 --all', target: null}.deferrals — the Pattern #13 matches from Step 2 (verbatim deferral_contract prose).handoff_artifacts — ['docs/vibe-test/coverage-<ISO>.md', '.vibe-test/state/coverage.json'].next_step_hint — persona-adapted. Default: "Run /vibe-test:gate when ready to check the tier threshold, or /vibe-test:generate to close gaps."Render three views in parallel:
renderMarkdown(report, {proseSlots}) → docs/vibe-test/coverage-<ISO-date>.md.renderBanner(report, {columns, disableColors: !isTty}) → printed to chat.renderJson({report, repoRoot}) → .vibe-test/state/coverage.json (scoped variant when --path set). Schema-validated against coverage-state.schema.json. This is the C3 machine-readable artifact — CI consumers read this file directly.Critical CI semantic (C3): the JSON sidecar carries passes_tier_threshold: boolean but the coverage command itself exits 0 regardless of pass/fail. The gate command (/vibe-test:gate) owns the exit-code contract. Coverage is a measurement, not a decision.
session-logger.end({sessionUUID, command: 'coverage', outcome: 'completed', key_decisions: [<adapter accepted/declined>, <denominator honest flag>], complements_invoked, artifact_generated}) — terminal entry paired to Step 0.beacons.append(repoRoot, {command: 'coverage', sessionUUID, outcome: 'completed', hint: '<score>/<threshold> - <pass|below>'}) — Pattern #12..vibe-test/state.json — persist the adapter decline flag if builder said no; otherwise leave coverage_adapter_declined unset. Coverage does not touch classification / inventory / gap lists (audit owns those).On any state-write failure: log a runtime_hook_failure friction entry and continue.
Persona-adapted handoff line per guide > "Handoff Language Rules":
| Persona | Handoff line |
|---|---|
professor | "When you're ready, run /vibe-test:gate for the tier-threshold check, or /vibe-test:generate if you want to close specific gaps first." |
cohort | "/vibe-test:gate next for CI check, or /vibe-test:generate to close gaps." |
superdev | "Run /vibe-test:gate." |
architect | "/vibe-test:gate for the tier-threshold verdict. JSON sidecar at .vibe-test/state/coverage.json is CI-ready." |
coach | "When you're ready, /vibe-test:gate will check whether this clears your tier threshold." |
null (default) | "Run /vibe-test:gate when ready." |
Do NOT prescribe /clear between commands. Claude Code auto-compacts.
| Knob | first-time / beginner | intermediate | experienced |
|---|---|---|---|
| Adapter-prompt framing | Analogy + diff + "fallback is c8 --all" | 2-line explanation + diff | Diff + [y/N] + fallback reference |
| Per-level table | Full table with plain-English level descriptions | Compact table | Path + percentage only |
| Cherry-picked-denominator rationale | Plain-English + list of missing files | One-line summary + missing count | Ratio + missing count |
JSON output is level-invariant.
| Trigger | friction_type | confidence |
|---|---|---|
| Builder declines the adapter proposal | coverage_adapter_refused | medium |
| Coverage run crashes (harness-break surfaced) | harness_break | high |
| Builder declines a Pattern #13 complement offer | complement_rejected | high |
| Cherry-picked denominator detected at runtime | cherry_picked_denominator | medium |
When in doubt, don't log. False positives poison /evolve.
passes_tier_threshold in the JSON sidecar but exits 0 regardless; /vibe-test:gate owns the exit-code contract./vibe-test:fix.tessl:analyzing-test-coverage.A score is only as honest as the denominator it's measured against. Most coverage tools report against "files the tests imported" because that's cheaper to compute — and it gives you 88% when the truth is 6%. The adapter-prompt UX is the product: it names the cherry-picked denominator problem out loud, shows the builder the exact diff that fixes it, and lets them opt in instead of silently mutating their config.
The c8 fallback exists because some builders won't want the config change (maybe their coverage config is tangled up in some CI pipeline, maybe they're auditing someone else's repo and don't want to edit anything). The fallback gives them the honest number without touching their code.
C3 — the JSON sidecar — is the machine-readable artifact. /vibe-test:gate reads it under CI mode; external CI systems can read it too. Keeping coverage's exit code at 0 regardless of pass/fail preserves the separation of concerns: coverage measures, gate decides.
Pattern #13 deferral to Tessl keeps Vibe Test honest about its own boundaries: if a dedicated coverage-analysis skill is installed, parsing raw coverage output is its job, not ours. We overlay tier-appropriate interpretation on top of the raw numbers — not re-parse them.
Provides UI/UX resources: 50+ styles, color palettes, font pairings, guidelines, charts for web/mobile across React, Next.js, Vue, Svelte, Tailwind, React Native, Flutter. Aids planning, building, reviewing interfaces.
Fetches up-to-date documentation from Context7 for libraries and frameworks like React, Next.js, Prisma. Use for setup questions, API references, and code examples.
npx claudepluginhub estevanhernandez-stack-ed/vibe-test --plugin vibe-test