Story 27: Coverage tracking (full-stack)

Add end-to-end coverage tracking: backend collects vitest coverage,
records metrics with threshold/baseline tracking, and blocks acceptance
on regression. Frontend displays coverage in gate/review panels with
a "Collect Coverage" button. Includes 20 Rust tests, 17 Vitest tests,
and 14 Playwright E2E tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dave
2026-02-19 14:45:57 +00:00
parent 8f0bc971bf
commit 8f684a6ca4
20 changed files with 1216 additions and 34 deletions

View File

@@ -27,6 +27,11 @@ interface GateState {
failed: number;
};
missingCategories: string[];
coverageReport: {
currentPercent: number;
thresholdPercent: number;
baselinePercent: number | null;
} | null;
}
export function Chat({ projectPath, onCloseProject }: ChatProps) {
@@ -54,6 +59,8 @@ export function Chat({ projectPath, onCloseProject }: ChatProps) {
const [proceedSuccess, setProceedSuccess] = useState<string | null>(null);
const [lastReviewRefresh, setLastReviewRefresh] = useState<Date | null>(null);
const [lastGateRefresh, setLastGateRefresh] = useState<Date | null>(null);
const [isCollectingCoverage, setIsCollectingCoverage] = useState(false);
const [coverageError, setCoverageError] = useState<string | null>(null);
const storyId = "26_establish_tdd_workflow_and_gates";
const gateStatusColor = isGateLoading
@@ -185,6 +192,14 @@ export function Chat({ projectPath, onCloseProject }: ChatProps) {
warning: response.warning ?? null,
summary: response.summary,
missingCategories: response.missing_categories,
coverageReport: response.coverage_report
? {
currentPercent: response.coverage_report.current_percent,
thresholdPercent: response.coverage_report.threshold_percent,
baselinePercent:
response.coverage_report.baseline_percent ?? null,
}
: null,
});
setLastGateRefresh(new Date());
})
@@ -254,6 +269,14 @@ export function Chat({ projectPath, onCloseProject }: ChatProps) {
warning: response.warning ?? null,
summary: response.summary,
missingCategories: response.missing_categories,
coverageReport: response.coverage_report
? {
currentPercent: response.coverage_report.current_percent,
thresholdPercent: response.coverage_report.threshold_percent,
baselinePercent:
response.coverage_report.baseline_percent ?? null,
}
: null,
});
setLastGateRefresh(new Date());
} catch (error) {
@@ -268,6 +291,21 @@ export function Chat({ projectPath, onCloseProject }: ChatProps) {
}
};
const handleCollectCoverage = async () => {
setIsCollectingCoverage(true);
setCoverageError(null);
try {
await workflowApi.collectCoverage({ story_id: storyId });
await refreshGateState(storyId);
} catch (error) {
const message =
error instanceof Error ? error.message : "Failed to collect coverage.";
setCoverageError(message);
} finally {
setIsCollectingCoverage(false);
}
};
const refreshReviewQueue = async () => {
setIsReviewLoading(true);
setReviewError(null);
@@ -549,8 +587,11 @@ export function Chat({ projectPath, onCloseProject }: ChatProps) {
gateStatusColor={gateStatusColor}
isGateLoading={isGateLoading}
gateError={gateError}
coverageError={coverageError}
lastGateRefresh={lastGateRefresh}
onRefresh={() => refreshGateState(storyId)}
onCollectCoverage={handleCollectCoverage}
isCollectingCoverage={isCollectingCoverage}
/>
</div>
</div>