feat(core-shared/conformance): manifest coverage schema + vitest helper
First implementation milestone of the agent-first coverage architecture (ADR-020, PRD 2026-05-13). Lands the keystone — coverage bands as a typed declaration in feature.manifest.ts plus a helper that derives vitest threshold shapes from them. New file packages/core-shared/src/conformance/coverage.ts (self- contained, no relative imports — loadable at vitest config time): - CoverageBand / CoverageBands / CoverageManifest / VitestThresholds types - DEFAULT_COVERAGE_BANDS (baseline 80/75/80/80; entities 100/100/100/ 100; use-cases + controllers 100/95/100/100) — matches ADR-011 - DEFAULT_MUTATION_SCORE (80) + DEFAULT_MUTATION_TARGETS (entities + use-cases) - getCoverageBands / getMutationConfig — manifest -> resolved bands, with default fallback for missing layers - vitestThresholdsFromBands / vitestThresholdsFromManifest — convert to vitest's coverage.thresholds shape with the layer-to-glob mapping define-feature.ts gains the optional coverage field on FeatureManifest (imports its type from coverage.ts to avoid a relative-import cycle at config-load time). Exposed via two subpaths: @repo/core-shared/conformance (re-exports for source/test code) and @repo/core-shared/conformance/coverage (direct subpath safe to load from vitest configs, bypasses the index re-export chain that Node ESM doesn't auto-extension-resolve). Auth wired as proof-of-concept: - packages/auth/src/feature.manifest.ts declares its coverage section - packages/auth/vitest.config.ts imports the helper + DEFAULT_COVERAGE_BANDS and emits thresholds via vitestThresholdsFromBands(DEFAULT_COVERAGE_BANDS) — no more hand-maintained per-glob thresholds block. Verified: 175/175 tests pass; 14/14 typechecks clean; auth coverage green (21 tests, 93.77% overall, all per-layer 100% bands hold). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,18 @@
|
||||
import { describe, it, expectTypeOf } from "vitest";
|
||||
import { defineFeature, type FeatureManifest } from "@/conformance/define-feature";
|
||||
import { describe, it, expect, expectTypeOf } from "vitest";
|
||||
import {
|
||||
defineFeature,
|
||||
type FeatureManifest,
|
||||
} from "@/conformance/define-feature";
|
||||
import {
|
||||
DEFAULT_COVERAGE_BANDS,
|
||||
DEFAULT_MUTATION_SCORE,
|
||||
DEFAULT_MUTATION_TARGETS,
|
||||
getCoverageBands,
|
||||
getMutationConfig,
|
||||
vitestThresholdsFromBands,
|
||||
vitestThresholdsFromManifest,
|
||||
type CoverageBand,
|
||||
} from "@/conformance/coverage";
|
||||
|
||||
describe("defineFeature", () => {
|
||||
it("preserves literal types of a manifest declared with `as const`", () => {
|
||||
@@ -27,7 +40,9 @@ describe("defineFeature", () => {
|
||||
// Literal preservation: name is the literal "auth", not string
|
||||
expectTypeOf(manifest.name).toEqualTypeOf<"auth">();
|
||||
// Use-case keys preserved
|
||||
expectTypeOf(manifest.useCases.signUp.audits).toEqualTypeOf<readonly ["user.created"]>();
|
||||
expectTypeOf(manifest.useCases.signUp.audits).toEqualTypeOf<
|
||||
readonly ["user.created"]
|
||||
>();
|
||||
expectTypeOf(manifest.useCases.signUp.mutates).toEqualTypeOf<true>();
|
||||
expectTypeOf(manifest.useCases.signIn.mutates).toEqualTypeOf<false>();
|
||||
});
|
||||
@@ -42,4 +57,213 @@ describe("defineFeature", () => {
|
||||
} as const);
|
||||
expectTypeOf(manifest).toMatchTypeOf<FeatureManifest>();
|
||||
});
|
||||
|
||||
it("accepts an optional coverage section", () => {
|
||||
const manifest = defineFeature({
|
||||
name: "auth",
|
||||
requiredCores: [],
|
||||
useCases: {},
|
||||
realtimeChannels: [],
|
||||
jobs: [],
|
||||
coverage: {
|
||||
bands: {
|
||||
baseline: { statements: 80, branches: 75, functions: 80, lines: 80 },
|
||||
entities: {
|
||||
statements: 100,
|
||||
branches: 100,
|
||||
functions: 100,
|
||||
lines: 100,
|
||||
},
|
||||
},
|
||||
mutationTargets: ["entities"],
|
||||
mutationScore: 85,
|
||||
},
|
||||
} as const);
|
||||
|
||||
expectTypeOf(manifest.coverage).toMatchTypeOf<
|
||||
| undefined
|
||||
| {
|
||||
readonly bands: {
|
||||
readonly baseline: CoverageBand;
|
||||
readonly entities?: CoverageBand;
|
||||
readonly "use-cases"?: CoverageBand;
|
||||
readonly controllers?: CoverageBand;
|
||||
};
|
||||
}
|
||||
>();
|
||||
expect(manifest.coverage?.mutationScore).toBe(85);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getCoverageBands", () => {
|
||||
const blankManifest: FeatureManifest = {
|
||||
name: "test",
|
||||
requiredCores: [],
|
||||
useCases: {},
|
||||
realtimeChannels: [],
|
||||
jobs: [],
|
||||
};
|
||||
|
||||
it("returns DEFAULT_COVERAGE_BANDS when manifest has no coverage section", () => {
|
||||
expect(getCoverageBands(blankManifest)).toEqual(DEFAULT_COVERAGE_BANDS);
|
||||
});
|
||||
|
||||
it("returns DEFAULT_COVERAGE_BANDS when coverage section is undefined", () => {
|
||||
expect(getCoverageBands({ ...blankManifest, coverage: undefined })).toEqual(
|
||||
DEFAULT_COVERAGE_BANDS,
|
||||
);
|
||||
});
|
||||
|
||||
it("uses declared baseline + fills missing layers from defaults", () => {
|
||||
const result = getCoverageBands({
|
||||
...blankManifest,
|
||||
coverage: {
|
||||
bands: {
|
||||
baseline: { statements: 90, branches: 85, functions: 90, lines: 90 },
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(result.baseline).toEqual({
|
||||
statements: 90,
|
||||
branches: 85,
|
||||
functions: 90,
|
||||
lines: 90,
|
||||
});
|
||||
expect(result.entities).toEqual(DEFAULT_COVERAGE_BANDS.entities);
|
||||
expect(result["use-cases"]).toEqual(DEFAULT_COVERAGE_BANDS["use-cases"]);
|
||||
expect(result.controllers).toEqual(DEFAULT_COVERAGE_BANDS.controllers);
|
||||
});
|
||||
|
||||
it("uses declared layer bands when present", () => {
|
||||
const customEntities: CoverageBand = {
|
||||
statements: 95,
|
||||
branches: 90,
|
||||
functions: 95,
|
||||
lines: 95,
|
||||
};
|
||||
const result = getCoverageBands({
|
||||
...blankManifest,
|
||||
coverage: {
|
||||
bands: {
|
||||
baseline: DEFAULT_COVERAGE_BANDS.baseline,
|
||||
entities: customEntities,
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(result.entities).toEqual(customEntities);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getMutationConfig", () => {
|
||||
const blankManifest: FeatureManifest = {
|
||||
name: "test",
|
||||
requiredCores: [],
|
||||
useCases: {},
|
||||
realtimeChannels: [],
|
||||
jobs: [],
|
||||
};
|
||||
|
||||
it("returns defaults when manifest has no coverage section", () => {
|
||||
const config = getMutationConfig(blankManifest);
|
||||
expect(config.targets).toEqual(DEFAULT_MUTATION_TARGETS);
|
||||
expect(config.score).toBe(DEFAULT_MUTATION_SCORE);
|
||||
});
|
||||
|
||||
it("honors declared mutationTargets + mutationScore", () => {
|
||||
const config = getMutationConfig({
|
||||
...blankManifest,
|
||||
coverage: {
|
||||
bands: { baseline: DEFAULT_COVERAGE_BANDS.baseline },
|
||||
mutationTargets: ["entities"],
|
||||
mutationScore: 90,
|
||||
},
|
||||
});
|
||||
expect(config.targets).toEqual(["entities"]);
|
||||
expect(config.score).toBe(90);
|
||||
});
|
||||
});
|
||||
|
||||
describe("DEFAULT_COVERAGE_BANDS", () => {
|
||||
it("matches the ADR-011 / ADR-020 documented bands", () => {
|
||||
expect(DEFAULT_COVERAGE_BANDS.baseline).toEqual({
|
||||
statements: 80,
|
||||
branches: 75,
|
||||
functions: 80,
|
||||
lines: 80,
|
||||
});
|
||||
expect(DEFAULT_COVERAGE_BANDS.entities).toEqual({
|
||||
statements: 100,
|
||||
branches: 100,
|
||||
functions: 100,
|
||||
lines: 100,
|
||||
});
|
||||
expect(DEFAULT_COVERAGE_BANDS["use-cases"]).toEqual({
|
||||
statements: 100,
|
||||
branches: 95,
|
||||
functions: 100,
|
||||
lines: 100,
|
||||
});
|
||||
expect(DEFAULT_COVERAGE_BANDS.controllers).toEqual({
|
||||
statements: 100,
|
||||
branches: 95,
|
||||
functions: 100,
|
||||
lines: 100,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("vitestThresholdsFromBands", () => {
|
||||
it("emits baseline at the top level and each declared layer under its glob", () => {
|
||||
const result = vitestThresholdsFromBands(DEFAULT_COVERAGE_BANDS);
|
||||
expect(result.statements).toBe(80);
|
||||
expect(result.branches).toBe(75);
|
||||
expect(result["src/entities/**"]).toEqual({
|
||||
statements: 100,
|
||||
branches: 100,
|
||||
functions: 100,
|
||||
lines: 100,
|
||||
});
|
||||
expect(result["src/application/use-cases/**"]).toEqual({
|
||||
statements: 100,
|
||||
branches: 95,
|
||||
functions: 100,
|
||||
lines: 100,
|
||||
});
|
||||
expect(result["src/interface-adapters/controllers/**"]).toEqual({
|
||||
statements: 100,
|
||||
branches: 95,
|
||||
functions: 100,
|
||||
lines: 100,
|
||||
});
|
||||
});
|
||||
|
||||
it("omits layer glob when its band is undefined", () => {
|
||||
const result = vitestThresholdsFromBands({
|
||||
baseline: DEFAULT_COVERAGE_BANDS.baseline,
|
||||
entities: DEFAULT_COVERAGE_BANDS.entities,
|
||||
});
|
||||
expect(result["src/entities/**"]).toBeDefined();
|
||||
expect(result["src/application/use-cases/**"]).toBeUndefined();
|
||||
expect(result["src/interface-adapters/controllers/**"]).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("vitestThresholdsFromManifest", () => {
|
||||
it("uses DEFAULT_COVERAGE_BANDS when the manifest omits coverage", () => {
|
||||
const manifest: FeatureManifest = {
|
||||
name: "test",
|
||||
requiredCores: [],
|
||||
useCases: {},
|
||||
realtimeChannels: [],
|
||||
jobs: [],
|
||||
};
|
||||
const result = vitestThresholdsFromManifest(manifest);
|
||||
expect(result.statements).toBe(80);
|
||||
expect(result["src/entities/**"]).toEqual({
|
||||
statements: 100,
|
||||
branches: 100,
|
||||
functions: 100,
|
||||
lines: 100,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user