Speccle

/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[] };
}