speckeeper 0.4.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,448 @@
1
+ import { z, ZodType } from 'zod';
2
+
3
+ /**
4
+ * Definition and validation of model relations
5
+ */
6
+
7
+ /**
8
+ * Model abstraction level
9
+ *
10
+ * - L0: Business + Domain Analysis (Why / Problem space)
11
+ * Desired outcomes/value, business flows, actors, terminology, business rules
12
+ * - L1: Requirements (What)
13
+ * Functional/non-functional requirements, constraints, acceptance criteria
14
+ * - L2: Design (How / Direction)
15
+ * Architecture, component breakdown, domain model, key sequences
16
+ * - L3: Detailed Design/Implementation (How to build / Concrete artifacts)
17
+ * Screen/API/DB definitions, external SSOT references
18
+ */
19
+ type ModelLevel = 'L0' | 'L1' | 'L2' | 'L3';
20
+ /**
21
+ * Get numeric index of level
22
+ */
23
+ declare function getLevelIndex(level: ModelLevel): number;
24
+ /**
25
+ * Relation types
26
+ */
27
+ declare const RELATION_TYPES: readonly ["dependsOn", "uses", "includes", "implements", "refines", "verifies", "verifiedBy", "satisfies", "traces", "relatedTo"];
28
+ type RelationType = typeof RELATION_TYPES[number];
29
+ /**
30
+ * Relation schema
31
+ */
32
+ declare const RelationSchema: z.ZodObject<{
33
+ /** Relation type */
34
+ type: z.ZodEnum<["dependsOn", "uses", "includes", "implements", "refines", "verifies", "verifiedBy", "satisfies", "traces", "relatedTo"]>;
35
+ /** Target ID */
36
+ target: z.ZodString;
37
+ /** Description (optional) */
38
+ description: z.ZodOptional<z.ZodString>;
39
+ }, "strip", z.ZodTypeAny, {
40
+ type: "includes" | "dependsOn" | "uses" | "implements" | "refines" | "verifies" | "verifiedBy" | "satisfies" | "traces" | "relatedTo";
41
+ target: string;
42
+ description?: string | undefined;
43
+ }, {
44
+ type: "includes" | "dependsOn" | "uses" | "implements" | "refines" | "verifies" | "verifiedBy" | "satisfies" | "traces" | "relatedTo";
45
+ target: string;
46
+ description?: string | undefined;
47
+ }>;
48
+ /**
49
+ * Relation field to add to model schemas
50
+ * Usage: schema.extend({ relations: RelationsFieldSchema })
51
+ */
52
+ declare const RelationsFieldSchema: z.ZodOptional<z.ZodArray<z.ZodObject<{
53
+ /** Relation type */
54
+ type: z.ZodEnum<["dependsOn", "uses", "includes", "implements", "refines", "verifies", "verifiedBy", "satisfies", "traces", "relatedTo"]>;
55
+ /** Target ID */
56
+ target: z.ZodString;
57
+ /** Description (optional) */
58
+ description: z.ZodOptional<z.ZodString>;
59
+ }, "strip", z.ZodTypeAny, {
60
+ type: "includes" | "dependsOn" | "uses" | "implements" | "refines" | "verifies" | "verifiedBy" | "satisfies" | "traces" | "relatedTo";
61
+ target: string;
62
+ description?: string | undefined;
63
+ }, {
64
+ type: "includes" | "dependsOn" | "uses" | "implements" | "refines" | "verifies" | "verifiedBy" | "satisfies" | "traces" | "relatedTo";
65
+ target: string;
66
+ description?: string | undefined;
67
+ }>, "many">>;
68
+ type Relation = z.infer<typeof RelationSchema>;
69
+ /**
70
+ * Definition of relation constraints
71
+ */
72
+ interface RelationConstraint {
73
+ /** Allowed target levels (all levels allowed if not specified) */
74
+ allowedTargetLevels?: ModelLevel[];
75
+ /** Level constraint rule */
76
+ levelRule: 'source>target' | 'source>=target' | 'same' | 'any';
77
+ /** Direction of impact propagation */
78
+ propagation: 'forward' | 'backward' | 'both';
79
+ }
80
+ /**
81
+ * Constraint definitions per relation type
82
+ *
83
+ * Level hierarchy:
84
+ * L0 (Business) → L1 (Requirements) → L2 (Design) → L3 (Implementation)
85
+ * Abstract ───────────────────────────→ Concrete
86
+ */
87
+ declare const RELATION_CONSTRAINTS: Record<RelationType, RelationConstraint>;
88
+ /**
89
+ * Relation validation error
90
+ */
91
+ interface RelationValidationError {
92
+ type: 'level_violation' | 'target_level_violation' | 'self_reference' | 'cycle_detected';
93
+ message: string;
94
+ relation: Relation;
95
+ sourceId: string;
96
+ }
97
+ /**
98
+ * Validate level constraint for single relation
99
+ *
100
+ * @param sourceLevel - Source model level (set in _models/)
101
+ * @param sourceSpecId - Source spec ID
102
+ * @param relation - Relation
103
+ * @param targetLevel - Target model level
104
+ */
105
+ declare function validateRelationLevel(sourceLevel: ModelLevel | undefined, sourceSpecId: string, relation: Relation, targetLevel: ModelLevel | undefined): RelationValidationError | null;
106
+ /**
107
+ * Detect circular references
108
+ */
109
+ declare function detectCycles(relations: Array<{
110
+ sourceId: string;
111
+ targetId: string;
112
+ type: RelationType;
113
+ }>): RelationValidationError[];
114
+ /**
115
+ * Infer model ID from spec ID (prefix-based)
116
+ */
117
+ declare function inferModelIdFromSpecId(specId: string): string | undefined;
118
+
119
+ /**
120
+ * Model base class
121
+ *
122
+ * All model definitions inherit from this class
123
+ */
124
+
125
+ /**
126
+ * Lint rule definition
127
+ */
128
+ interface LintRule<T> {
129
+ id: string;
130
+ severity: 'error' | 'warning' | 'info';
131
+ message: string;
132
+ check: (spec: T) => boolean;
133
+ }
134
+ /**
135
+ * Lint result
136
+ */
137
+ interface LintResult {
138
+ ruleId: string;
139
+ severity: 'error' | 'warning' | 'info';
140
+ message: string;
141
+ specId?: string;
142
+ }
143
+ /**
144
+ * Exporter definition
145
+ */
146
+ interface Exporter<T> {
147
+ format: 'markdown' | 'json' | 'mermaid';
148
+ single?: (spec: T) => string;
149
+ index?: (specs: T[]) => string;
150
+ outputDir?: string;
151
+ filename?: (spec: T) => string;
152
+ }
153
+ /**
154
+ * External checker definition
155
+ */
156
+ interface ExternalChecker<T> {
157
+ targetType: string;
158
+ sourcePath: (spec: T) => string;
159
+ check: (spec: T, externalData: unknown) => CheckResult;
160
+ }
161
+ /**
162
+ * Check result
163
+ */
164
+ interface CheckResult {
165
+ success: boolean;
166
+ errors: {
167
+ message: string;
168
+ field?: string;
169
+ specId?: string;
170
+ }[];
171
+ warnings: {
172
+ message: string;
173
+ field?: string;
174
+ specId?: string;
175
+ }[];
176
+ }
177
+ /**
178
+ * Coverage result
179
+ */
180
+ interface CoverageResult {
181
+ /** Total target count */
182
+ total: number;
183
+ /** Covered count */
184
+ covered: number;
185
+ /** Uncovered count */
186
+ uncovered: number;
187
+ /** Coverage rate (%) */
188
+ coveragePercent: number;
189
+ /** Details of covered items */
190
+ coveredItems: {
191
+ id: string;
192
+ description?: string;
193
+ }[];
194
+ /** Details of uncovered items */
195
+ uncoveredItems: {
196
+ id: string;
197
+ description?: string;
198
+ sourceId?: string;
199
+ }[];
200
+ }
201
+ /**
202
+ * Coverage checker definition
203
+ *
204
+ * Verify cross-model consistency (coverage).
205
+ * Example: Whether TestRef covers acceptanceCriteria of Requirement
206
+ */
207
+ interface CoverageChecker<T> {
208
+ /** Target model ID for coverage (e.g. 'requirement') */
209
+ targetModel: string;
210
+ /** Description of coverage check */
211
+ description: string;
212
+ /** Execute coverage check */
213
+ check: (specs: T[], registry: Record<string, Map<string, unknown>>) => CoverageResult;
214
+ }
215
+ /**
216
+ * Render context
217
+ * Simplified version compatible with embedoc's EmbedContext
218
+ */
219
+ interface RenderContext {
220
+ /** Parameters (filter conditions other than format, etc.) */
221
+ params: Record<string, string | undefined>;
222
+ /** Markdown helpers */
223
+ markdown: {
224
+ /** Generate table */
225
+ table: (headers: string[], rows: (string | unknown)[][]) => string;
226
+ };
227
+ }
228
+ /**
229
+ * Renderer definition
230
+ *
231
+ * Model-specific rendering called from embeds
232
+ */
233
+ interface Renderer<T> {
234
+ /** Format ID ('table', 'list', 'detail', 'spec-chapter', etc.) */
235
+ format: string;
236
+ /** Rendering process */
237
+ render: (specs: T[], ctx: RenderContext) => string;
238
+ }
239
+ /**
240
+ * Model base class
241
+ *
242
+ * @template TSchema - Zod schema type
243
+ */
244
+ declare abstract class Model<TSchema extends ZodType> {
245
+ /** Singleton instance storage (per subclass) */
246
+ private static _instances;
247
+ /**
248
+ * Get singleton instance of the model
249
+ * Usage: RequirementModel.instance
250
+ */
251
+ static get instance(): Model<ZodType>;
252
+ /** Model ID ('requirement', 'usecase', etc.) */
253
+ abstract readonly id: string;
254
+ /** Model name ('Requirement', 'UseCase', etc.) */
255
+ abstract readonly name: string;
256
+ /** ID prefix ('REQ', 'UC', etc.) */
257
+ abstract readonly idPrefix: string;
258
+ /** Zod schema */
259
+ abstract readonly schema: TSchema;
260
+ /** Model description (optional) */
261
+ readonly description?: string;
262
+ /** External SSOT type (optional, e.g. 'OpenAPI', 'DDL/Prisma') */
263
+ readonly externalSsotType?: string;
264
+ /** Spec instance type */
265
+ protected get specType(): z.infer<TSchema>;
266
+ /** Lint rules (override in subclass) */
267
+ protected lintRules: LintRule<z.infer<TSchema>>[];
268
+ /** Exporters (override in subclass) */
269
+ protected exporters: Exporter<z.infer<TSchema>>[];
270
+ /** External checker (optional) */
271
+ protected externalChecker?: ExternalChecker<z.infer<TSchema>>;
272
+ /** Coverage checker (optional) */
273
+ protected coverageChecker?: CoverageChecker<z.infer<TSchema>>;
274
+ /** Model level (set in _models/) */
275
+ protected modelLevel?: ModelLevel;
276
+ /** Renderers (for embeds, override in subclass) */
277
+ protected renderers: Renderer<z.infer<TSchema>>[];
278
+ /**
279
+ * Get model level
280
+ * Returns modelLevel set in _models/
281
+ */
282
+ get level(): ModelLevel | undefined;
283
+ /**
284
+ * Execute schema validation
285
+ */
286
+ validate(spec: unknown): z.infer<TSchema>;
287
+ /**
288
+ * Schema validation (safe version, returns error object on failure)
289
+ */
290
+ safeParse(spec: unknown): {
291
+ success: true;
292
+ data: z.infer<TSchema>;
293
+ } | {
294
+ success: false;
295
+ error: z.ZodError;
296
+ };
297
+ /**
298
+ * Execute lint
299
+ */
300
+ lint(spec: z.infer<TSchema>): LintResult[];
301
+ /**
302
+ * Execute lint for multiple specs
303
+ */
304
+ lintAll(specs: z.infer<TSchema>[]): LintResult[];
305
+ /**
306
+ * Validate level constraints of relations
307
+ * @param spec - Spec instance (if it has relations property)
308
+ * @returns Array of validation errors
309
+ */
310
+ validateRelations(spec: z.infer<TSchema>): RelationValidationError[];
311
+ /**
312
+ * Validate relations of multiple specs (including circular reference check)
313
+ */
314
+ validateAllRelations(specs: z.infer<TSchema>[]): RelationValidationError[];
315
+ /**
316
+ * Export in specified format (single spec)
317
+ */
318
+ exportSingle(spec: z.infer<TSchema>, format: string): string | null;
319
+ /**
320
+ * Export in specified format (index)
321
+ */
322
+ exportIndex(specs: z.infer<TSchema>[], format: string): string | null;
323
+ /**
324
+ * Get exporter output directory
325
+ */
326
+ getOutputDir(format: string): string | null;
327
+ /**
328
+ * Get filename
329
+ */
330
+ getFilename(spec: z.infer<TSchema>, format: string): string | null;
331
+ /**
332
+ * Check consistency with external SSOT
333
+ */
334
+ check(spec: z.infer<TSchema>, externalData: unknown): CheckResult;
335
+ /**
336
+ * Get external SSOT path
337
+ */
338
+ getExternalSourcePath(spec: z.infer<TSchema>): string | null;
339
+ /**
340
+ * Execute coverage check
341
+ * @param specs - Array of spec instances for this model
342
+ * @param registry - Registry of all models
343
+ */
344
+ checkCoverage(specs: z.infer<TSchema>[], registry: Record<string, Map<string, unknown>>): CoverageResult | null;
345
+ /**
346
+ * Get coverage checker
347
+ */
348
+ getCoverageChecker(): CoverageChecker<z.infer<TSchema>> | undefined;
349
+ /**
350
+ * Get lint rules
351
+ */
352
+ getLintRules(): LintRule<z.infer<TSchema>>[];
353
+ /**
354
+ * Get exporters
355
+ */
356
+ getExporters(): Exporter<z.infer<TSchema>>[];
357
+ /**
358
+ * Render in specified format
359
+ * @param format - Format ID ('table', 'list', 'detail', etc.)
360
+ * @param specs - Array of specs to render
361
+ * @param ctx - Render context
362
+ * @returns Rendered result (Markdown string), null if no matching renderer
363
+ */
364
+ render(format: string, specs: z.infer<TSchema>[], ctx: RenderContext): string | null;
365
+ /**
366
+ * Get list of available formats
367
+ */
368
+ getAvailableFormats(): string[];
369
+ /**
370
+ * Get renderers
371
+ */
372
+ getRenderers(): Renderer<z.infer<TSchema>>[];
373
+ /**
374
+ * Check if renderer exists for specific format
375
+ */
376
+ hasRenderer(format: string): boolean;
377
+ /**
378
+ * Register spec instances for this model.
379
+ * Stores specs in the global specStore keyed by this model's id.
380
+ */
381
+ register(specs: z.infer<TSchema>[]): void;
382
+ }
383
+ /**
384
+ * A single (Model, data[]) pair.
385
+ * Uses structural typing (not nominal Model<any>) so that
386
+ * compiled dist/ Model instances are compatible with src/ types.
387
+ */
388
+ interface SpecEntry {
389
+ model: {
390
+ id: string;
391
+ register: (data: unknown[]) => void;
392
+ };
393
+ data: unknown[];
394
+ }
395
+ /**
396
+ * Common export interface for spec data files.
397
+ * Each spec file exports a SpecModule via defineSpecs().
398
+ */
399
+ interface SpecModule {
400
+ entries: SpecEntry[];
401
+ }
402
+ /**
403
+ * Aggregated result from mergeSpecs().
404
+ */
405
+ interface MergedDesign {
406
+ models: SpecEntry['model'][];
407
+ specs: SpecEntry[];
408
+ }
409
+ /**
410
+ * Define spec data in a spec file. Returns a SpecModule.
411
+ *
412
+ * @example
413
+ * ```typescript
414
+ * export default defineSpecs(
415
+ * [FunctionalRequirementModel.instance, functionalRequirements],
416
+ * [NonFunctionalRequirementModel.instance, nonFunctionalRequirements],
417
+ * );
418
+ * ```
419
+ */
420
+ declare function defineSpecs(...entries: [SpecEntry['model'], unknown[]][]): SpecModule;
421
+ /**
422
+ * Merge multiple SpecModules into a single MergedDesign.
423
+ * Models are deduplicated by model.id.
424
+ *
425
+ * @example
426
+ * ```typescript
427
+ * // design/index.ts
428
+ * export default mergeSpecs(requirements, usecases, glossary);
429
+ * ```
430
+ */
431
+ declare function mergeSpecs(...modules: SpecModule[]): MergedDesign;
432
+ /**
433
+ * Build a registry (modelId -> Map<specId, spec>) from config.specs.
434
+ * Pure function — no global state.
435
+ */
436
+ declare function buildRegistryFromConfig(specs: SpecEntry[] | undefined): Record<string, Map<string, unknown>>;
437
+ /**
438
+ * Get specs for a model ID from config.specs.
439
+ * Pure function — no global state.
440
+ */
441
+ declare function getSpecsFromConfig(specs: SpecEntry[] | undefined, modelId: string): unknown[];
442
+ /**
443
+ * Find which model type a spec ID belongs to, from config.specs.
444
+ * Pure function — no global state.
445
+ */
446
+ declare function findModelTypeFromConfig(specs: SpecEntry[] | undefined, specId: string): string | null;
447
+
448
+ export { type CheckResult as C, type Exporter as E, type LintResult as L, type MergedDesign as M, RELATION_CONSTRAINTS as R, type SpecEntry as S, type CoverageChecker as a, type CoverageResult as b, type ExternalChecker as c, type LintRule as d, Model as e, type ModelLevel as f, RELATION_TYPES as g, type Relation as h, RelationSchema as i, type RelationValidationError as j, RelationsFieldSchema as k, type RenderContext as l, type Renderer as m, type SpecModule as n, buildRegistryFromConfig as o, defineSpecs as p, detectCycles as q, findModelTypeFromConfig as r, getLevelIndex as s, getSpecsFromConfig as t, inferModelIdFromSpecId as u, mergeSpecs as v, validateRelationLevel as w };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "speckeeper",
3
- "version": "0.4.2",
3
+ "version": "0.6.0",
4
4
  "description": "TypeScript-first specification validation framework with external SSOT integration",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -61,6 +61,7 @@
61
61
  "commander": "^12.1.0",
62
62
  "embedoc": "^0.10.1",
63
63
  "glob": "^10.3.10",
64
+ "node-sql-parser": "^5.4.0",
64
65
  "tsx": "^4.21.0",
65
66
  "yaml": "^2.3.4",
66
67
  "zod": "^3.22.4"
@@ -68,6 +69,7 @@
68
69
  "devDependencies": {
69
70
  "@eslint/js": "^9.39.2",
70
71
  "@types/node": "^20.11.0",
72
+ "@types/node-sql-parser": "^1.0.0",
71
73
  "@typescript-eslint/eslint-plugin": "^8.54.0",
72
74
  "@typescript-eslint/parser": "^8.54.0",
73
75
  "@vitest/coverage-v8": "^1.6.1",