speckeeper
v0.9.2
Published
TypeScript-first specification validation framework with external SSOT integration
Maintainers
Readme
speckeeper
TypeScript-first specification validation framework — validate design consistency and external SSOT integrity with full traceability.
Why speckeeper?
Requirements and design documents often drift from implementation. speckeeper treats specifications as code — type-safe, version-controlled, and continuously validated against your actual artifacts (tests, OpenAPI, DDL, IaC).
speckeeper.config.ts (sources)
│
├─► Global Source Scan → Find spec IDs in OpenAPI / DDL / annotations
│ │
│ ▼
│ MatchMap (specId → matches)
│ │
│ ▼
├─► Deep Validation → Model-level structural checks (optional)
│
design/*.ts
│
├─► speckeeper lint → Design integrity (IDs, references, phase gates)
├─► speckeeper check → External SSOT validation (global scan + deep validation)
└─► speckeeper impact → Change impact analysis with traceabilityFeatures
- TypeScript as SSOT — Define requirements, architecture, and design in type-safe TypeScript
- Design validation — Lint rules for ID uniqueness, reference integrity, circular dependencies, and phase gates
- External SSOT validation — Global scan across OpenAPI, DDL, annotations; optional deep validation per model
- Traceability — Track relationships across model levels (L0-L3) with impact analysis
- Scaffold from Mermaid — Generate
_models/skeletons from a mermaid flowchart with class-based artifact resolution - Custom models — Extend with domain-specific models (Runbooks, Policies, etc.)
- CI-ready — Built-in lint, drift detection, and coverage checks
Installation
npm install speckeeper
# Verify installation
npx speckeeper --helpQuick Start
1. Define your metamodel as a Mermaid flowchart
Create a Markdown file (e.g. requirements.md) containing a mermaid flowchart that describes the relationships between your specification entities:
flowchart TB
subgraph L0[Business]
UC[Use Cases]
TERM[Glossary]
end
subgraph L1[Requirements]
FR[Functional Requirements]
NFR[Non-Functional Requirements]
end
subgraph External[External Artifacts]
API[OpenAPI Spec]
DDL[Database Schema]
UT[Unit Tests]
end
FR -->|refines| UC
FR -->|implements| API
FR -->|verifiedBy| UT
FR -->|implements| DDL
class UC,TERM,FR,NFR speckeeper
class FR,NFR requirement
class UC usecase
class TERM term
class API openapi
class DDL sqlschema
class UT testKey concepts:
class ... speckeepermarks nodes as managed by speckeeper- Additional
classlines assign artifact classes (determines model name/file and node grouping) - External node classes (
openapi,sqlschema,test) describe the artifact type subgraphdetermines model level (L0–L3)implements/verifiedByedges define the relationship semantics
2. Scaffold models
npx speckeeper scaffold --source requirements.mdThis generates:
design/_models/— Model classes with base schema and lint rulesdesign/*.ts— Spec data files usingdefineSpecs()design/index.ts— Entry point viamergeSpecs()
See Scaffold Mermaid Specification for the full input format.
3. Fill in your specifications
Edit spec data files in design/ to add your actual specification data. Each file uses defineSpecs() to pair Model instances with data:
// design/requirements.ts
import { defineSpecs } from 'speckeeper';
import type { Requirement } from './_models/requirement';
import { FunctionalRequirementModel } from './_models/requirement';
const requirements: Requirement[] = [
{
id: 'FR-001',
name: 'User Authentication',
type: 'functional',
description: 'Users can authenticate using email and password',
priority: 'must',
acceptanceCriteria: [
{ id: 'FR-001-01', description: 'Valid credentials grant access', verificationMethod: 'test' },
{ id: 'FR-001-02', description: 'Invalid credentials show error', verificationMethod: 'test' },
],
},
];
export default defineSpecs(
[FunctionalRequirementModel.instance, requirements],
);design/index.ts aggregates all spec files, and speckeeper.config.ts imports the result — no manual wiring needed beyond adding your spec file to design/index.ts.
4. Run validation
# Validate design integrity
npx speckeeper lint
# Check test coverage against requirements
npx speckeeper check test --coverage
# Analyze change impact
npx speckeeper impact FR-001Alternative:
npx speckeeper initcreates a minimal project with generic starter templates. Use this if you prefer to build models from scratch. See Model Definition Guide for details.
CLI Commands
| Command | Description |
|---------|-------------|
| speckeeper init | Initialize a new project with starter templates |
| speckeeper lint | Validate design integrity (ID uniqueness, references, phase gates) |
| speckeeper check | Verify consistency with external SSOT |
| speckeeper check test --coverage | Verify test coverage for requirements |
| speckeeper scaffold | Generate model skeletons from a mermaid flowchart |
| speckeeper drift | Detect manual edits to generated docs/ files |
| speckeeper impact <id> | Analyze change impact for a specific element |
Note: speckeeper build generates machine-readable specs/ output. For human-readable docs (docs/), use embedoc or similar tools with the model rendering API.
Validation Features
Design Integrity (lint)
$ npx speckeeper lint
speckeeper lint
Design: design/
Loaded: 17 files
Running lint checks...
✓ No issues foundChecks include:
- ID uniqueness — No duplicate IDs within model types
- ID conventions — Enforce naming patterns (e.g.,
FR-001,COMP-AUTH) - Reference integrity — All referenced IDs must exist
- Circular dependency detection — Prevent reference loops
- Phase gates — Ensure TBD items are resolved by target phase
- Custom lint rules — Define model-specific validation
External SSOT Validation (check)
Validate your specifications against actual implementation artifacts. speckeeper performs a global source scan across all configured sources, then optionally runs deep validation using model-specific rules.
The check flow has three levels:
- Existence check (automatic) — Is the spec ID found in any configured source?
- Structural check (via
deepValidation) — Does the matched object's structure match? (e.g. HTTP method, table columns) - Type check (via
deepValidation) — Do types match? (e.g. parameter types, column types)
Source configuration
Define global scan sources in speckeeper.config.ts:
// speckeeper.config.ts
import { defineConfig } from 'speckeeper';
export default defineConfig({
// ...
sources: [
{
type: 'openapi',
paths: ['api/openapi.yaml'],
relation: 'implements',
},
{
type: 'ddl',
paths: ['db/schema.sql'],
relation: 'implements',
},
{
type: 'annotation',
paths: ['test/**/*.test.ts', 'tests/**/*.test.ts'],
relation: 'verifiedBy',
},
{
type: 'annotation',
paths: ['src/**/*.ts'],
exclude: ['src/**/*.test.ts'],
relation: 'implements',
},
],
});Each source defines:
type— Built-in ('openapi','ddl','annotation') or custom with ascannerpluginpaths— Glob patterns for files to scanrelation— Whether matches represent'implements'or'verifiedBy'
Built-in scanners
| Scanner | Finds spec IDs via | Deep validation |
|---------|-------------------|-----------------|
| openapi | operationId, path segment, schema name, x-spec-id | HTTP method, parameter names/types, response property names/types |
| ddl | Table name (case-insensitive, schema-prefix stripped) | Column names, column types (containment-based) |
| annotation | @verifies, @implements, @traces annotations | — |
Annotations work in any comment style (//, #, --, /* */, <!-- -->). Multiple IDs can be comma- or space-separated.
// tests/unit/auth.test.ts
// @verifies FR-001, FR-001-01
describe('User Authentication', () => { ... });// src/auth/handler.ts
// @implements FR-001
export class AuthHandler { ... }Deep validation (optional)
Models can define deepValidation to enable Level 2/3 structural checks on matched source objects:
class EntityModel extends Model<typeof EntitySchema> {
// ... schema, lintRules, etc.
protected deepValidation: DeepValidationConfig<Entity> = {
ddl: {
mapper: (spec) => ({
tableName: spec.tableName,
columns: spec.columns.map(c => ({ name: c.name, type: c.type })),
checkTypes: true,
}),
},
openapi: {
mapper: (spec) => ({
path: spec.apiPath,
method: spec.httpMethod,
responseProperties: spec.fields.map(f => ({ name: f.name, type: f.type })),
}),
},
};
}Without deepValidation, speckeeper still performs existence checks for all spec IDs across all configured sources.
Lookup keys (when spec ID differs from external identifier)
By default, the global scanner searches for each spec's id in external sources. When the external identifier differs — for example, entity ID "user" vs DDL table name "users" — define lookupKeys on the model to map per source type:
class EntityModel extends Model<typeof EntitySchema> {
readonly id = 'entity';
readonly name = 'Entity';
readonly idPrefix = 'ENT';
readonly schema = EntitySchema;
protected lookupKeys: LookupKeyConfig<Entity> = {
ddl: (spec) => spec.tableName,
openapi: (spec) => spec.schemaName ?? spec.id,
};
}With this configuration, when scanning DDL sources the scanner searches for spec.tableName instead of spec.id. If a match is found, the result is mapped back to the original spec ID for reporting and deep validation.
lookupKeys is optional per source type — any source type not listed falls back to spec.id.
Custom scanners
For file formats not covered by the built-in scanners, provide a custom SourceScanner plugin:
// speckeeper.config.ts
import { defineConfig } from 'speckeeper';
import type { SourceScanner } from 'speckeeper';
const protoScanner: SourceScanner = {
findSpecIds(content, specIds, filePath) {
// Parse protobuf content, find spec IDs in service/message names
// Return SourceMatch[] with specId, location, and optional context
return [];
},
};
export default defineConfig({
sources: [
{ type: 'proto', paths: ['proto/**/*.proto'], relation: 'implements', scanner: protoScanner },
// ... other sources
],
});$ npx speckeeper check --verbose
speckeeper check
Design: design/
Type: all
✓ All checks passedTransitive coverage
Narrative-level specs (e.g. UseCases) rarely have direct @verifies UC-001 annotations in code. Instead, they are verified indirectly through a chain: UseCase is satisfied by Requirements, and those Requirements are verified by tests.
Configure coverage.transitiveRelations to enable automatic transitive coverage:
// speckeeper.config.ts
export default defineConfig({
sources: [/* ... */],
coverage: {
transitiveRelations: ['satisfies'],
},
});When speckeeper check --coverage runs:
- The global scan determines which specs are directly covered (found in external sources)
- For each transitive relation type, the framework walks the relation graph
- A spec is transitively covered if ALL specs that relate to it via a transitive relation are themselves covered (directly or transitively)
No per-model code is needed. Coverage is computed purely from relation data and config.
$ npx speckeeper check test --coverage
Transitive coverage (via satisfies)
─────────────────────────────────────
Total: 12
Covered: 11 (8 direct + 3 transitive)
Uncovered: 1
Coverage: 92%Multi-level chains are supported. For example, with transitiveRelations: ['satisfies', 'verifies'], if TEST-001 verifies FR-001, and FR-001 satisfies UC-001, then UC-001 is transitively covered when TEST-001 is directly matched.
Model Levels & Traceability
speckeeper organizes models by abstraction level:
| Level | Focus | Examples | |-------|-------|----------| | L0 | Business + Domain (Why) | UseCase, Actor, Term | | L1 | Requirements (What) | Requirement, Constraint | | L2 | Design (How) | Component, Entity, Layer | | L3 | Implementation (Build) | Screen, APIRef, TableRef |
Relations between models enable impact analysis:
$ npx speckeeper impact FR-001
FR-001 (Requirement)
├── implements: COMP-AUTH (Component)
├── satisfies: UC-001 (UseCase)
└── verifiedBy: TEST-001 (TestRef)Relation Types
| Relation | Direction | Description |
|----------|-----------|-------------|
| implements | spec→external | Spec is implemented as external artifact (OpenAPI, DDL) |
| verifiedBy | spec→test | Spec is verified by external test code |
| satisfies | L1→L0 | Satisfies a use case |
| refines | Same level or lower | Refinement |
| verifies | test→implementation | Test verifies implementation code (external, no checker) |
| dependsOn | None | Dependency |
| relatedTo | None | Association |
See Model Entity Catalog for full details on relation types and level constraints.
Customizing Models
Scaffolded models provide a base schema (id, name, description, relations). You can customize them or add new domain-specific models using core factory functions from speckeeper/dsl:
import { z } from 'zod';
import { Model, RelationSchema } from 'speckeeper';
import type { LintRule, Exporter, ModelLevel } from 'speckeeper';
import { requireField, arrayMinLength } from 'speckeeper/dsl';
const RunbookSchema = z.object({
id: z.string(),
name: z.string().min(1),
description: z.string(),
severity: z.enum(['critical', 'high', 'medium', 'low']),
steps: z.array(z.object({
action: z.string(),
verification: z.string().optional(),
})).min(1),
relations: z.array(RelationSchema).optional(),
});
type Runbook = z.input<typeof RunbookSchema>;
class RunbookModel extends Model<typeof RunbookSchema> {
readonly id = 'runbook';
readonly name = 'Runbook';
readonly idPrefix = 'RB';
readonly schema = RunbookSchema;
readonly description = 'Incident runbooks';
protected modelLevel: ModelLevel = 'L3';
protected lintRules: LintRule<Runbook>[] = [
requireField<Runbook>('description', 'error'),
arrayMinLength<Runbook>('steps', 1),
];
protected exporters: Exporter<Runbook>[] = [];
}Core DSL factories (speckeeper/dsl) include requireField, arrayMinLength, idFormat, childIdFormat, markdownExporter, annotationCoverage, relationCoverage, and baseSpecSchema. Global scanner utilities (openapiScanner, ddlScanner, annotationScanner, createAnnotationScanner) are also re-exported for advanced use.
Documentation
- Model Definition Guide — Start here for model customization and API reference
- Framework Requirements Specification — Detailed feature specifications
- Model Entity Catalog — Model hierarchy and relation types
Compatibility
- Node.js >= 20.0.0
- TypeScript >= 5.0
Contributing
# Install dependencies
npm install
# Run tests
npm test
# Lint
npm run lint
npm run lint:design
# Full CI check
npm run ciLicense
MIT
