/tools/core
@speccle/core
Shipped[tool]The shared foundation every tool depends on. It holds only what every tool shares: the ReportModel contract, the [AC-n] convention, and parseSpec. Pure, no LLM.
The [AC-n] convention
Every acceptance criterion in a spec carries a bracketed token — [AC-1], [AC-2], … — and a test links to a criterion when its title contains that token. This one convention is the thread that ties spec, tests, and mutants together; everything downstream is a join over it.
spec.md
## Arithmetic - [AC-1] add returns the sum of its two arguments. - [AC-2] sub returns the difference of its two arguments.
parseSpec extracts the criteria — id, title, section — in spec reading order.
The ReportModel contract
The report model is the contract between the engine and any renderer — and the action list a feedback-loop agent consumes. These types are the public shape; everything else in core is an implementation detail. This site's own demo renders exactly this JSON.
packages/core/src/model.ts
export type CriterionStatus = "strong" | "weak" | "unverified" | "inert";
export type MutantStatus = "killed" | "survived";
export interface Mutant {
file: string;
line: number;
mutator: string;
original: string;
replacement: string;
status: MutantStatus;
}
export interface Criterion {
id: string;
title: string;
section?: string;
status: CriterionStatus;
/** killed / covered; null when no covered mutants. */
killRate: number | null;
counts: { covered: number; killed: number; survived: number };
linkedTests: string[];
mutants: Mutant[];
}
export interface ReportModel {
meta: { projectName: string; generatedAt: string; specPath: string };
headline: {
/** 0-100, naïve line coverage from the coverage summary. */
lineCoverage: number;
/** 0-100, killed / TOTAL mutants — the brutal denominator. */
oracleStrength: number;
totals: { mutants: number; killed: number; survived: number; uncovered: number };
};
/** Criteria in spec reading order. */
criteria: Criterion[];
unattributed: { count: number; mutants: Mutant[] };
}