speckeeper 0.1.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.
package/dist/index.js ADDED
@@ -0,0 +1,1364 @@
1
+ import { z } from 'zod';
2
+ import { existsSync, readFileSync, mkdirSync, writeFileSync, readdirSync, statSync } from 'fs';
3
+ import { resolve, join, dirname, extname, basename } from 'path';
4
+ import { parse } from 'yaml';
5
+ import { createHash } from 'crypto';
6
+ import { glob } from 'glob';
7
+
8
+ // src/types/common.ts
9
+ var PhaseSchema = z.enum(["REQ", "HLD", "LLD", "IMPL", "OPS", "CI"]);
10
+ var PrioritySchema = z.enum(["must", "should", "could", "wont"]);
11
+ var createRequirementId = (id) => id;
12
+ var createComponentId = (id) => id;
13
+ var createBoundaryId = (id) => id;
14
+ var createLayerId = (id) => id;
15
+ var createEntityId = (id) => id;
16
+ var createRelationId = (id) => id;
17
+ var createRuleId = (id) => id;
18
+ var createScreenId = (id) => id;
19
+ var createTransitionId = (id) => id;
20
+ var createFormId = (id) => id;
21
+ var createUIComponentId = (id) => id;
22
+ var createProcessFlowId = (id) => id;
23
+ var createUseCaseId = (id) => id;
24
+ var createActorId = (id) => id;
25
+ var createDTORequirementId = (id) => id;
26
+ var createEventRequirementId = (id) => id;
27
+ var createAPIRefId = (id) => id;
28
+ var createTableRefId = (id) => id;
29
+ var createIaCRefId = (id) => id;
30
+ var createBatchRefId = (id) => id;
31
+ var createErrorCaseId = (id) => id;
32
+ var createTermId = (id) => id;
33
+ var createStateId = (id) => id;
34
+ var ID_PATTERNS = {
35
+ requirement: /^REQ-[A-Z]+-\d{6}-[A-Z0-9]{4}$/,
36
+ component: /^COMP-[A-Z]+-[A-Z0-9-]+$/,
37
+ boundary: /^BND-[A-Z]+-[A-Z0-9-]+$/,
38
+ layer: /^LAYER-[A-Z]+$/,
39
+ entity: /^ENT-[A-Z]+-?[A-Z0-9-]*$/,
40
+ relation: /^REL-[A-Z]+-[A-Z0-9-]+$/,
41
+ rule: /^RULE-[A-Z0-9-]+$/,
42
+ screen: /^SCR-[A-Z]+-?[A-Z0-9-]*$/,
43
+ transition: /^TRANS-[A-Z0-9-]+$/,
44
+ form: /^FORM-[A-Z0-9-]+$/,
45
+ uiComponent: /^UI-[A-Z0-9-]+$/,
46
+ processFlow: /^FLOW-[A-Z]+-[A-Z0-9-]+$/,
47
+ useCase: /^UC-[A-Z]+-\d{3}$/,
48
+ actor: /^ACTOR-[A-Z]+$/,
49
+ dtoRequirement: /^DTO-REQ-[A-Z0-9-]+$/,
50
+ eventRequirement: /^EVT-REQ-[A-Z0-9-]+$/,
51
+ apiRef: /^API-[A-Z0-9-]+$/,
52
+ tableRef: /^TBL-[A-Z0-9-]+$/,
53
+ iacRef: /^IAC-[A-Z0-9-]+$/,
54
+ batchRef: /^BATCH-[A-Z0-9-]+$/,
55
+ errorCase: /^ERR-[A-Z]+-\d{3}$/,
56
+ term: /^TERM-[A-Z0-9-]+$/
57
+ };
58
+ var ConcretizationSlotSchema = z.object({
59
+ field: z.string(),
60
+ mustDecideBy: PhaseSchema,
61
+ value: z.union([z.string(), z.null()]).optional(),
62
+ description: z.string().optional()
63
+ });
64
+ var LinkSchema = z.object({
65
+ type: z.enum(["requirement", "component", "entity", "screen", "usecase", "external"]),
66
+ id: z.string(),
67
+ label: z.string().optional()
68
+ });
69
+ var ReferenceDefinitionSchema = z.object({
70
+ field: z.string(),
71
+ targetModel: z.string(),
72
+ required: z.boolean().optional(),
73
+ isArray: z.boolean().optional()
74
+ });
75
+ var LintIssueSchema = z.object({
76
+ message: z.string(),
77
+ field: z.string().optional(),
78
+ suggestion: z.string().optional()
79
+ });
80
+ var CheckErrorSchema = z.object({
81
+ message: z.string(),
82
+ modelId: z.string(),
83
+ externalRef: z.string().optional()
84
+ });
85
+ var CheckWarningSchema = z.object({
86
+ message: z.string(),
87
+ modelId: z.string()
88
+ });
89
+ var CheckResultSchema = z.object({
90
+ success: z.boolean(),
91
+ errors: z.array(CheckErrorSchema),
92
+ warnings: z.array(CheckWarningSchema)
93
+ });
94
+ function getLevelIndex(level) {
95
+ return ["L0", "L1", "L2", "L3"].indexOf(level);
96
+ }
97
+ var RELATION_TYPES = [
98
+ "dependsOn",
99
+ "uses",
100
+ "includes",
101
+ "implements",
102
+ "refines",
103
+ "verifies",
104
+ "satisfies",
105
+ "traces",
106
+ "relatedTo"
107
+ ];
108
+ var RelationSchema = z.object({
109
+ /** Relation type */
110
+ type: z.enum(RELATION_TYPES),
111
+ /** Target ID */
112
+ target: z.string(),
113
+ /** Description (optional) */
114
+ description: z.string().optional()
115
+ });
116
+ var RelationsFieldSchema = z.array(RelationSchema).optional();
117
+ var RELATION_CONSTRAINTS = {
118
+ // Concrete→Abstract (implementation/design fulfills requirements)
119
+ "implements": {
120
+ allowedTargetLevels: ["L1"],
121
+ // Implements requirements
122
+ levelRule: "source>target",
123
+ propagation: "forward"
124
+ // A changes → B needs review
125
+ },
126
+ // Concrete→Abstract (design/implementation satisfies business/requirements)
127
+ "satisfies": {
128
+ allowedTargetLevels: ["L0", "L1"],
129
+ // Satisfies business/requirements
130
+ levelRule: "source>=target",
131
+ propagation: "backward"
132
+ // B changes → A needs update
133
+ },
134
+ // Concrete→Abstract (refinement: requirements refine business, design refines requirements)
135
+ "refines": {
136
+ allowedTargetLevels: ["L0", "L1"],
137
+ // Refines business/requirements
138
+ levelRule: "source>target",
139
+ propagation: "backward"
140
+ // B changes → A needs update
141
+ },
142
+ // Test→Requirements/Business (verification relationship)
143
+ "verifies": {
144
+ allowedTargetLevels: ["L0", "L1"],
145
+ // Verifies business/requirements
146
+ levelRule: "any",
147
+ propagation: "backward"
148
+ // B changes → A needs update
149
+ },
150
+ // Dependencies (same level or concrete→abstract)
151
+ "dependsOn": {
152
+ levelRule: "source>=target",
153
+ propagation: "backward"
154
+ // B changes → A affected
155
+ },
156
+ // Usage relationship (no level constraint)
157
+ "uses": {
158
+ levelRule: "any",
159
+ propagation: "backward"
160
+ // B changes → A affected
161
+ },
162
+ // Inclusion relationship (same level only)
163
+ "includes": {
164
+ levelRule: "same",
165
+ propagation: "backward"
166
+ // B changes → A affected
167
+ },
168
+ // Bidirectional tracing
169
+ "traces": {
170
+ levelRule: "any",
171
+ propagation: "both"
172
+ },
173
+ // General relation
174
+ "relatedTo": {
175
+ levelRule: "any",
176
+ propagation: "both"
177
+ }
178
+ };
179
+ function validateRelationLevel(sourceLevel, sourceSpecId, relation, targetLevel) {
180
+ if (sourceSpecId === relation.target) {
181
+ return {
182
+ type: "self_reference",
183
+ message: `Self-reference is not allowed: ${sourceSpecId} \u2192 ${relation.target}`,
184
+ relation,
185
+ sourceId: sourceSpecId
186
+ };
187
+ }
188
+ if (!sourceLevel || !targetLevel) {
189
+ return null;
190
+ }
191
+ const constraint = RELATION_CONSTRAINTS[relation.type];
192
+ if (constraint.allowedTargetLevels && !constraint.allowedTargetLevels.includes(targetLevel)) {
193
+ return {
194
+ type: "target_level_violation",
195
+ message: `'${relation.type}' relation target must be ${constraint.allowedTargetLevels.join(" or ")}, but target is ${targetLevel}`,
196
+ relation,
197
+ sourceId: sourceSpecId
198
+ };
199
+ }
200
+ const srcIdx = getLevelIndex(sourceLevel);
201
+ const tgtIdx = getLevelIndex(targetLevel);
202
+ switch (constraint.levelRule) {
203
+ case "source>target":
204
+ if (srcIdx <= tgtIdx) {
205
+ return {
206
+ type: "level_violation",
207
+ message: `'${relation.type}' requires source (${sourceLevel}) to be more concrete than target (${targetLevel})`,
208
+ relation,
209
+ sourceId: sourceSpecId
210
+ };
211
+ }
212
+ break;
213
+ case "source>=target":
214
+ if (srcIdx < tgtIdx) {
215
+ return {
216
+ type: "level_violation",
217
+ message: `'${relation.type}' requires source (${sourceLevel}) to be same or more concrete than target (${targetLevel})`,
218
+ relation,
219
+ sourceId: sourceSpecId
220
+ };
221
+ }
222
+ break;
223
+ case "same":
224
+ if (srcIdx !== tgtIdx) {
225
+ return {
226
+ type: "level_violation",
227
+ message: `'${relation.type}' requires source (${sourceLevel}) and target (${targetLevel}) to be same level`,
228
+ relation,
229
+ sourceId: sourceSpecId
230
+ };
231
+ }
232
+ break;
233
+ }
234
+ return null;
235
+ }
236
+ function detectCycles(relations) {
237
+ const errors = [];
238
+ const graph = /* @__PURE__ */ new Map();
239
+ const relationMap = /* @__PURE__ */ new Map();
240
+ for (const rel of relations) {
241
+ if (!graph.has(rel.sourceId)) {
242
+ graph.set(rel.sourceId, /* @__PURE__ */ new Set());
243
+ }
244
+ graph.get(rel.sourceId).add(rel.targetId);
245
+ relationMap.set(`${rel.sourceId}\u2192${rel.targetId}`, { type: rel.type, target: rel.targetId });
246
+ }
247
+ const visited = /* @__PURE__ */ new Set();
248
+ const recursionStack = /* @__PURE__ */ new Set();
249
+ const cyclePath = [];
250
+ function dfs(node) {
251
+ visited.add(node);
252
+ recursionStack.add(node);
253
+ cyclePath.push(node);
254
+ const neighbors = graph.get(node);
255
+ if (neighbors) {
256
+ for (const neighbor of neighbors) {
257
+ if (!visited.has(neighbor)) {
258
+ if (dfs(neighbor)) {
259
+ return true;
260
+ }
261
+ } else if (recursionStack.has(neighbor)) {
262
+ const cycleStart = cyclePath.indexOf(neighbor);
263
+ const cycle = cyclePath.slice(cycleStart);
264
+ cycle.push(neighbor);
265
+ errors.push({
266
+ type: "cycle_detected",
267
+ message: `Circular reference detected: ${cycle.join(" \u2192 ")}`,
268
+ relation: relationMap.get(`${node}\u2192${neighbor}`),
269
+ sourceId: node
270
+ });
271
+ return true;
272
+ }
273
+ }
274
+ }
275
+ cyclePath.pop();
276
+ recursionStack.delete(node);
277
+ return false;
278
+ }
279
+ for (const node of graph.keys()) {
280
+ if (!visited.has(node)) {
281
+ dfs(node);
282
+ }
283
+ }
284
+ return errors;
285
+ }
286
+ function inferModelIdFromSpecId(specId) {
287
+ const prefixMap = {
288
+ "REQ": "requirement",
289
+ "CR": "constraint",
290
+ "UC": "usecase",
291
+ "ACTOR": "actor",
292
+ "TERM": "term",
293
+ "COMP": "component",
294
+ "ENT": "entity",
295
+ "FLOW": "process-flow",
296
+ "LAYER": "layer",
297
+ "BND": "boundary",
298
+ "SCR": "screen",
299
+ "API": "api-ref",
300
+ "TBL": "table-ref",
301
+ "IAC": "iac-ref",
302
+ "BATCH": "batch-ref"
303
+ };
304
+ for (const [prefix, modelId] of Object.entries(prefixMap)) {
305
+ if (specId.startsWith(prefix + "-")) {
306
+ return modelId;
307
+ }
308
+ }
309
+ return void 0;
310
+ }
311
+
312
+ // src/core/model.ts
313
+ var Model = class {
314
+ /** Singleton instance storage (per subclass) */
315
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
316
+ static _instances = /* @__PURE__ */ new WeakMap();
317
+ /**
318
+ * Get singleton instance of the model
319
+ * Usage: RequirementModel.instance
320
+ */
321
+ static get instance() {
322
+ const ctor = this;
323
+ if (!this._instances.has(ctor)) {
324
+ this._instances.set(ctor, new ctor());
325
+ }
326
+ return this._instances.get(ctor);
327
+ }
328
+ /** Model description (optional) */
329
+ description;
330
+ /** External SSOT type (optional, e.g. 'OpenAPI', 'DDL/Prisma') */
331
+ externalSsotType;
332
+ /** Spec instance type */
333
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
334
+ get specType() {
335
+ return void 0;
336
+ }
337
+ /** Lint rules (override in subclass) */
338
+ lintRules = [];
339
+ /** Exporters (override in subclass) */
340
+ exporters = [];
341
+ /** External checker (optional) */
342
+ externalChecker;
343
+ /** Coverage checker (optional) */
344
+ coverageChecker;
345
+ /** Model level (set in _models/) */
346
+ modelLevel;
347
+ /** Renderers (for embeds, override in subclass) */
348
+ renderers = [];
349
+ /**
350
+ * Get model level
351
+ * Returns modelLevel set in _models/
352
+ */
353
+ get level() {
354
+ return this.modelLevel;
355
+ }
356
+ /**
357
+ * Execute schema validation
358
+ */
359
+ validate(spec) {
360
+ return this.schema.parse(spec);
361
+ }
362
+ /**
363
+ * Schema validation (safe version, returns error object on failure)
364
+ */
365
+ safeParse(spec) {
366
+ return this.schema.safeParse(spec);
367
+ }
368
+ /**
369
+ * Execute lint
370
+ */
371
+ lint(spec) {
372
+ const specId = spec.id;
373
+ return this.lintRules.filter((rule) => rule.check(spec)).map((rule) => ({
374
+ ruleId: rule.id,
375
+ severity: rule.severity,
376
+ message: rule.message,
377
+ specId
378
+ }));
379
+ }
380
+ /**
381
+ * Execute lint for multiple specs
382
+ */
383
+ lintAll(specs) {
384
+ return specs.flatMap((spec) => this.lint(spec));
385
+ }
386
+ /**
387
+ * Validate level constraints of relations
388
+ * @param spec - Spec instance (if it has relations property)
389
+ * @returns Array of validation errors
390
+ */
391
+ validateRelations(spec) {
392
+ const errors = [];
393
+ const specWithRelations = spec;
394
+ if (!specWithRelations.relations || !specWithRelations.id) {
395
+ return errors;
396
+ }
397
+ const allModels = getAllModels();
398
+ const modelLevelMap = /* @__PURE__ */ new Map();
399
+ for (const model of allModels) {
400
+ modelLevelMap.set(model.id, model.level);
401
+ }
402
+ for (const relation of specWithRelations.relations) {
403
+ const targetModelId = inferModelIdFromSpecId(relation.target);
404
+ if (!targetModelId) {
405
+ continue;
406
+ }
407
+ const targetLevel = modelLevelMap.get(targetModelId);
408
+ const error = validateRelationLevel(
409
+ this.level,
410
+ specWithRelations.id,
411
+ relation,
412
+ targetLevel
413
+ );
414
+ if (error) {
415
+ errors.push(error);
416
+ }
417
+ }
418
+ return errors;
419
+ }
420
+ /**
421
+ * Validate relations of multiple specs (including circular reference check)
422
+ */
423
+ validateAllRelations(specs) {
424
+ const errors = [];
425
+ const allRelations = [];
426
+ for (const spec of specs) {
427
+ errors.push(...this.validateRelations(spec));
428
+ const specWithRelations = spec;
429
+ if (specWithRelations.relations && specWithRelations.id) {
430
+ for (const rel of specWithRelations.relations) {
431
+ allRelations.push({
432
+ sourceId: specWithRelations.id,
433
+ targetId: rel.target,
434
+ type: rel.type
435
+ });
436
+ }
437
+ }
438
+ }
439
+ errors.push(...detectCycles(allRelations));
440
+ return errors;
441
+ }
442
+ /**
443
+ * Export in specified format (single spec)
444
+ */
445
+ exportSingle(spec, format) {
446
+ const exporter = this.exporters.find((e) => e.format === format);
447
+ if (!exporter?.single) return null;
448
+ return exporter.single(spec);
449
+ }
450
+ /**
451
+ * Export in specified format (index)
452
+ */
453
+ exportIndex(specs, format) {
454
+ const exporter = this.exporters.find((e) => e.format === format);
455
+ if (!exporter?.index) return null;
456
+ return exporter.index(specs);
457
+ }
458
+ /**
459
+ * Get exporter output directory
460
+ */
461
+ getOutputDir(format) {
462
+ const exporter = this.exporters.find((e) => e.format === format);
463
+ return exporter?.outputDir ?? null;
464
+ }
465
+ /**
466
+ * Get filename
467
+ */
468
+ getFilename(spec, format) {
469
+ const exporter = this.exporters.find((e) => e.format === format);
470
+ if (!exporter?.filename) return null;
471
+ return exporter.filename(spec);
472
+ }
473
+ /**
474
+ * Check consistency with external SSOT
475
+ */
476
+ check(spec, externalData) {
477
+ if (!this.externalChecker) {
478
+ return { success: true, errors: [], warnings: [] };
479
+ }
480
+ return this.externalChecker.check(spec, externalData);
481
+ }
482
+ /**
483
+ * Get external SSOT path
484
+ */
485
+ getExternalSourcePath(spec) {
486
+ if (!this.externalChecker) return null;
487
+ return this.externalChecker.sourcePath(spec);
488
+ }
489
+ /**
490
+ * Execute coverage check
491
+ * @param specs - Array of spec instances for this model
492
+ * @param registry - Registry of all models
493
+ */
494
+ checkCoverage(specs, registry) {
495
+ if (!this.coverageChecker) return null;
496
+ return this.coverageChecker.check(specs, registry);
497
+ }
498
+ /**
499
+ * Get coverage checker
500
+ */
501
+ getCoverageChecker() {
502
+ return this.coverageChecker;
503
+ }
504
+ /**
505
+ * Get lint rules
506
+ */
507
+ getLintRules() {
508
+ return this.lintRules;
509
+ }
510
+ /**
511
+ * Get exporters
512
+ */
513
+ getExporters() {
514
+ return this.exporters;
515
+ }
516
+ // ============================================================================
517
+ // Renderer (for embeds)
518
+ // ============================================================================
519
+ /**
520
+ * Render in specified format
521
+ * @param format - Format ID ('table', 'list', 'detail', etc.)
522
+ * @param specs - Array of specs to render
523
+ * @param ctx - Render context
524
+ * @returns Rendered result (Markdown string), null if no matching renderer
525
+ */
526
+ render(format, specs, ctx) {
527
+ const renderer = this.renderers.find((r) => r.format === format);
528
+ if (!renderer) return null;
529
+ return renderer.render(specs, ctx);
530
+ }
531
+ /**
532
+ * Get list of available formats
533
+ */
534
+ getAvailableFormats() {
535
+ return this.renderers.map((r) => r.format);
536
+ }
537
+ /**
538
+ * Get renderers
539
+ */
540
+ getRenderers() {
541
+ return this.renderers;
542
+ }
543
+ /**
544
+ * Check if renderer exists for specific format
545
+ */
546
+ hasRenderer(format) {
547
+ return this.renderers.some((r) => r.format === format);
548
+ }
549
+ };
550
+ var modelRegistry = /* @__PURE__ */ new Map();
551
+ function registerModel(model) {
552
+ modelRegistry.set(model.id, model);
553
+ }
554
+ function getModel(id) {
555
+ return modelRegistry.get(id);
556
+ }
557
+ function getAllModels() {
558
+ return Array.from(modelRegistry.values());
559
+ }
560
+ function clearModelRegistry() {
561
+ modelRegistry.clear();
562
+ }
563
+
564
+ // src/core/model-registry.ts
565
+ var globalRegistry = {
566
+ definitions: /* @__PURE__ */ new Map(),
567
+ instances: /* @__PURE__ */ new Map()
568
+ };
569
+ function registerModelDefinition(definition) {
570
+ if (globalRegistry.definitions.has(definition.id)) {
571
+ console.warn(`Model definition '${definition.id}' is already registered. Overwriting.`);
572
+ }
573
+ globalRegistry.definitions.set(definition.id, definition);
574
+ if (!globalRegistry.instances.has(definition.id)) {
575
+ globalRegistry.instances.set(definition.id, /* @__PURE__ */ new Map());
576
+ }
577
+ }
578
+ function registerModelDefinitions(definitions) {
579
+ for (const def of definitions) {
580
+ registerModelDefinition(def);
581
+ }
582
+ }
583
+ function getModelDefinition(modelId) {
584
+ return globalRegistry.definitions.get(modelId);
585
+ }
586
+ function getAllModelDefinitions() {
587
+ return Array.from(globalRegistry.definitions.values());
588
+ }
589
+ function registerModelInstance(modelId, instance) {
590
+ const definition = globalRegistry.definitions.get(modelId);
591
+ if (!definition) {
592
+ throw new Error(`Model definition '${modelId}' not found. Register the definition first.`);
593
+ }
594
+ const result = definition.schema.safeParse(instance);
595
+ if (!result.success) {
596
+ throw new Error(
597
+ `Invalid instance for model '${modelId}': ${result.error.message}`
598
+ );
599
+ }
600
+ let instances = globalRegistry.instances.get(modelId);
601
+ if (!instances) {
602
+ instances = /* @__PURE__ */ new Map();
603
+ globalRegistry.instances.set(modelId, instances);
604
+ }
605
+ instances.set(instance.id, instance);
606
+ }
607
+ function getModelInstance(modelId, instanceId) {
608
+ const instances = globalRegistry.instances.get(modelId);
609
+ return instances?.get(instanceId);
610
+ }
611
+ function getAllModelInstances(modelId) {
612
+ const instances = globalRegistry.instances.get(modelId);
613
+ if (!instances) return [];
614
+ return Array.from(instances.values());
615
+ }
616
+ function getAllInstances() {
617
+ const result = [];
618
+ for (const [modelId, instances] of globalRegistry.instances) {
619
+ for (const instance of instances.values()) {
620
+ result.push({ modelId, instance });
621
+ }
622
+ }
623
+ return result;
624
+ }
625
+ function getGlobalModelRegistry() {
626
+ return globalRegistry;
627
+ }
628
+ function resetModelRegistry() {
629
+ globalRegistry.definitions.clear();
630
+ globalRegistry.instances.clear();
631
+ }
632
+ function resetModelInstances() {
633
+ for (const instances of globalRegistry.instances.values()) {
634
+ instances.clear();
635
+ }
636
+ }
637
+ function lintModel(modelId, instance, context) {
638
+ const definition = globalRegistry.definitions.get(modelId);
639
+ if (!definition || !definition.lintRules) return [];
640
+ const issues = [];
641
+ for (const rule of definition.lintRules) {
642
+ const issue = rule.check(instance, context);
643
+ if (issue) {
644
+ issues.push({ rule, issue });
645
+ }
646
+ }
647
+ return issues;
648
+ }
649
+ function lintAllModels(context) {
650
+ const allIssues = [];
651
+ for (const [modelId, instances] of globalRegistry.instances) {
652
+ const definition = globalRegistry.definitions.get(modelId);
653
+ if (!definition || !definition.lintRules) continue;
654
+ for (const [instanceId, instance] of instances) {
655
+ for (const rule of definition.lintRules) {
656
+ const issue = rule.check(instance, context);
657
+ if (issue) {
658
+ allIssues.push({
659
+ modelId,
660
+ instanceId,
661
+ rule,
662
+ issue
663
+ });
664
+ }
665
+ }
666
+ }
667
+ }
668
+ return allIssues;
669
+ }
670
+ function exportMarkdown(modelId, context) {
671
+ const definition = globalRegistry.definitions.get(modelId);
672
+ if (!definition || !definition.exporters?.markdown) return [];
673
+ const exporter = definition.exporters.markdown;
674
+ const instances = getAllModelInstances(modelId);
675
+ const files = [];
676
+ for (const instance of instances) {
677
+ const content = exporter.single(instance, context);
678
+ const filename = exporter.filename ? exporter.filename(instance) : instance.id;
679
+ files.push({
680
+ path: `${context.outputDir}/${exporter.outputDir}/${filename}.md`,
681
+ content
682
+ });
683
+ }
684
+ if (exporter.index && instances.length > 0) {
685
+ const indexContent = exporter.index(instances, context);
686
+ files.push({
687
+ path: `${context.outputDir}/${exporter.outputDir}/index.md`,
688
+ content: indexContent
689
+ });
690
+ }
691
+ return files;
692
+ }
693
+ function exportMermaid(modelId, context) {
694
+ const definition = globalRegistry.definitions.get(modelId);
695
+ if (!definition || !definition.exporters?.mermaid) return [];
696
+ const exporters = Array.isArray(definition.exporters.mermaid) ? definition.exporters.mermaid : [definition.exporters.mermaid];
697
+ const instances = getAllModelInstances(modelId);
698
+ const files = [];
699
+ for (const exporter of exporters) {
700
+ const content = exporter.generate(instances, context);
701
+ files.push({
702
+ path: `${context.outputDir}/${exporter.outputPath}`,
703
+ content
704
+ });
705
+ }
706
+ return files;
707
+ }
708
+ function exportAllModels(context) {
709
+ const files = [];
710
+ for (const modelId of globalRegistry.definitions.keys()) {
711
+ files.push(...exportMarkdown(modelId, context));
712
+ files.push(...exportMermaid(modelId, context));
713
+ }
714
+ return files;
715
+ }
716
+ function checkExternalSsot(modelId, externalSource, context) {
717
+ const definition = globalRegistry.definitions.get(modelId);
718
+ if (!definition || !definition.externalChecker) {
719
+ return { success: true, errors: [], warnings: [] };
720
+ }
721
+ const checker = definition.externalChecker;
722
+ const instances = getAllModelInstances(modelId);
723
+ const allErrors = [];
724
+ const allWarnings = [];
725
+ for (const instance of instances) {
726
+ const result = checker.check(instance, externalSource, context);
727
+ allErrors.push(...result.errors);
728
+ allWarnings.push(...result.warnings);
729
+ }
730
+ return {
731
+ success: allErrors.length === 0,
732
+ errors: allErrors,
733
+ warnings: allWarnings
734
+ };
735
+ }
736
+ function resolveReference(modelId, instanceId, field) {
737
+ const definition = globalRegistry.definitions.get(modelId);
738
+ if (!definition || !definition.references) return void 0;
739
+ const refDef = definition.references.find((r) => r.field === field);
740
+ if (!refDef) return void 0;
741
+ const instance = getModelInstance(modelId, instanceId);
742
+ if (!instance) return void 0;
743
+ const refValue = instance[field];
744
+ if (!refValue) return void 0;
745
+ if (refDef.isArray && Array.isArray(refValue)) {
746
+ return refValue.map((id) => getModelInstance(refDef.targetModel, id));
747
+ }
748
+ return getModelInstance(refDef.targetModel, refValue);
749
+ }
750
+ function checkReferenceIntegrity() {
751
+ const issues = [];
752
+ for (const [modelId, definition] of globalRegistry.definitions) {
753
+ if (!definition.references) continue;
754
+ const instances = globalRegistry.instances.get(modelId);
755
+ if (!instances) continue;
756
+ for (const [instanceId, instance] of instances) {
757
+ const inst = instance;
758
+ for (const refDef of definition.references) {
759
+ const refValue = inst[refDef.field];
760
+ if (!refValue) {
761
+ if (refDef.required) {
762
+ issues.push({
763
+ modelId,
764
+ instanceId,
765
+ field: refDef.field,
766
+ targetModel: refDef.targetModel,
767
+ missingIds: ["(required but not provided)"]
768
+ });
769
+ }
770
+ continue;
771
+ }
772
+ const targetInstances = globalRegistry.instances.get(refDef.targetModel);
773
+ const missingIds = [];
774
+ if (refDef.isArray && Array.isArray(refValue)) {
775
+ for (const id of refValue) {
776
+ if (!targetInstances?.has(id)) {
777
+ missingIds.push(id);
778
+ }
779
+ }
780
+ } else {
781
+ if (!targetInstances?.has(refValue)) {
782
+ missingIds.push(refValue);
783
+ }
784
+ }
785
+ if (missingIds.length > 0) {
786
+ issues.push({
787
+ modelId,
788
+ instanceId,
789
+ field: refDef.field,
790
+ targetModel: refDef.targetModel,
791
+ missingIds
792
+ });
793
+ }
794
+ }
795
+ }
796
+ }
797
+ return issues;
798
+ }
799
+ function getModelRegistryStats() {
800
+ const instanceCounts = {};
801
+ let totalInstances = 0;
802
+ for (const [modelId, instances] of globalRegistry.instances) {
803
+ instanceCounts[modelId] = instances.size;
804
+ totalInstances += instances.size;
805
+ }
806
+ return {
807
+ definitionCount: globalRegistry.definitions.size,
808
+ instanceCounts,
809
+ totalInstances
810
+ };
811
+ }
812
+
813
+ // src/core/config-api.ts
814
+ function defineConfig(input) {
815
+ const resolved = {
816
+ ...input,
817
+ designDir: input.designDir ?? "design",
818
+ docsDir: input.docsDir ?? "docs",
819
+ specsDir: input.specsDir ?? "specs"
820
+ };
821
+ if (input.models && input.models.length > 0) {
822
+ registerModelDefinitions(input.models);
823
+ }
824
+ return resolved;
825
+ }
826
+ function defineModel(input) {
827
+ const definition = {
828
+ id: input.id,
829
+ name: input.name,
830
+ description: input.description,
831
+ schema: input.schema,
832
+ idPrefix: input.idPrefix,
833
+ exporters: input.exporters,
834
+ lintRules: input.lintRules,
835
+ externalChecker: input.externalChecker,
836
+ references: input.references,
837
+ dslName: input.dslName,
838
+ phase: input.phase
839
+ };
840
+ return definition;
841
+ }
842
+ function createMarkdownExporter(config) {
843
+ return {
844
+ single: config.single,
845
+ index: config.index,
846
+ outputDir: config.outputDir,
847
+ filename: config.filename
848
+ };
849
+ }
850
+ function createMermaidExporter(config) {
851
+ return {
852
+ diagramType: config.diagramType,
853
+ outputPath: config.outputPath,
854
+ generate: config.generate,
855
+ title: config.title
856
+ };
857
+ }
858
+ function createLintRule(config) {
859
+ return {
860
+ id: config.id,
861
+ description: config.description,
862
+ severity: config.severity,
863
+ check: config.check
864
+ };
865
+ }
866
+ async function loadSpeckeeperConfig(configPath) {
867
+ const possiblePaths = configPath ? [configPath] : [
868
+ "speckeeper.config.ts",
869
+ "speckeeper.config.js",
870
+ "speckeeper.config.mjs"
871
+ ];
872
+ for (const path of possiblePaths) {
873
+ try {
874
+ const fullPath = path.startsWith("/") ? path : `${process.cwd()}/${path}`;
875
+ const module = await import(fullPath);
876
+ const config = module.default || module;
877
+ if (config && typeof config === "object") {
878
+ if (config.models && Array.isArray(config.models)) {
879
+ registerModelDefinitions(config.models);
880
+ }
881
+ return {
882
+ ...config,
883
+ designDir: config.designDir ?? "design",
884
+ docsDir: config.docsDir ?? "docs",
885
+ specsDir: config.specsDir ?? "specs"
886
+ };
887
+ }
888
+ } catch {
889
+ }
890
+ }
891
+ return null;
892
+ }
893
+ var defaultConfig = {
894
+ srcDir: "src",
895
+ designDir: "design",
896
+ // Directory for requirements/design TS models
897
+ docsDir: "docs",
898
+ specsDir: "specs"
899
+ };
900
+ function getOutputPaths(config, cwd = process.cwd()) {
901
+ const docsDir = resolve(cwd, config.docsDir);
902
+ const specsDir = resolve(cwd, config.specsDir);
903
+ return {
904
+ // docs/ subdirectories (compliant with spec section 7.1)
905
+ requirements: join(docsDir, "requirements"),
906
+ usecases: join(docsDir, "usecases"),
907
+ architecture: join(docsDir, "architecture"),
908
+ dataModel: join(docsDir, "data-model"),
909
+ screens: join(docsDir, "screens"),
910
+ flows: join(docsDir, "flows"),
911
+ glossary: join(docsDir, "glossary"),
912
+ component: join(docsDir, "component"),
913
+ // specs/ subdirectories (compliant with spec section 7.1)
914
+ schemasEntities: join(specsDir, "schemas", "entities")
915
+ };
916
+ }
917
+ function getExpectedOutputDirs(config, cwd = process.cwd()) {
918
+ const paths = getOutputPaths(config, cwd);
919
+ return [
920
+ paths.requirements,
921
+ paths.usecases,
922
+ paths.architecture,
923
+ paths.dataModel,
924
+ paths.screens,
925
+ paths.flows,
926
+ paths.glossary,
927
+ paths.component,
928
+ paths.schemasEntities
929
+ ];
930
+ }
931
+ function validateOutputPaths(actualPath, config, cwd = process.cwd()) {
932
+ const expectedDirs = getExpectedOutputDirs(config, cwd);
933
+ const docsDir = resolve(cwd, config.docsDir);
934
+ const specsDir = resolve(cwd, config.specsDir);
935
+ const isUnderDocs = actualPath.startsWith(docsDir);
936
+ const isUnderSpecs = actualPath.startsWith(specsDir);
937
+ if (!isUnderDocs && !isUnderSpecs) {
938
+ return {
939
+ valid: false,
940
+ expectedPaths: expectedDirs,
941
+ message: `Output path "${actualPath}" is not under docs/ or specs/`
942
+ };
943
+ }
944
+ const isValidPath = expectedDirs.some((dir) => actualPath.startsWith(dir));
945
+ if (!isValidPath) {
946
+ return {
947
+ valid: false,
948
+ expectedPaths: expectedDirs,
949
+ message: `Output path "${actualPath}" does not match spec-compliant paths. Expected one of: ${expectedDirs.map((d) => d.replace(cwd + "/", "")).join(", ")}`
950
+ };
951
+ }
952
+ return { valid: true, expectedPaths: expectedDirs };
953
+ }
954
+ var CONFIG_FILES = [
955
+ "speckeeper.config.yaml",
956
+ "speckeeper.config.yml",
957
+ "speckeeper.config.json",
958
+ "speckeeper.config.ts",
959
+ "speckeeper.config.js"
960
+ ];
961
+ function findConfigFile(startDir = process.cwd()) {
962
+ let currentDir = startDir;
963
+ while (currentDir !== dirname(currentDir)) {
964
+ for (const configFile of CONFIG_FILES) {
965
+ const configPath = resolve(currentDir, configFile);
966
+ if (existsSync(configPath)) {
967
+ return configPath;
968
+ }
969
+ }
970
+ currentDir = dirname(currentDir);
971
+ }
972
+ return null;
973
+ }
974
+ async function loadConfig(configPath, cwd = process.cwd()) {
975
+ const resolvedPath = configPath ?? findConfigFile(cwd);
976
+ if (!resolvedPath) {
977
+ return { ...defaultConfig };
978
+ }
979
+ const ext = resolvedPath.split(".").pop()?.toLowerCase();
980
+ try {
981
+ if (ext === "yaml" || ext === "yml") {
982
+ const content = readFileSync(resolvedPath, "utf-8");
983
+ const parsed = parse(content);
984
+ return { ...defaultConfig, ...parsed };
985
+ }
986
+ if (ext === "json") {
987
+ const content = readFileSync(resolvedPath, "utf-8");
988
+ const parsed = JSON.parse(content);
989
+ return { ...defaultConfig, ...parsed };
990
+ }
991
+ if (ext === "ts" || ext === "js") {
992
+ const module = await import(resolvedPath);
993
+ const parsed = module.default ?? module;
994
+ return { ...defaultConfig, ...parsed };
995
+ }
996
+ return { ...defaultConfig };
997
+ } catch (error) {
998
+ console.error(`Failed to load config from ${resolvedPath}:`, error);
999
+ return { ...defaultConfig };
1000
+ }
1001
+ }
1002
+ function validateConfig(config) {
1003
+ const errors = [];
1004
+ if (!config.srcDir) {
1005
+ errors.push("srcDir is required");
1006
+ }
1007
+ if (!config.docsDir) {
1008
+ errors.push("docsDir is required");
1009
+ }
1010
+ if (!config.specsDir) {
1011
+ errors.push("specsDir is required");
1012
+ }
1013
+ return errors;
1014
+ }
1015
+ function ensureDir(dirPath) {
1016
+ if (!existsSync(dirPath)) {
1017
+ mkdirSync(dirPath, { recursive: true });
1018
+ }
1019
+ }
1020
+ function writeFile(filePath, content) {
1021
+ const dir = dirname(filePath);
1022
+ ensureDir(dir);
1023
+ const existed = existsSync(filePath);
1024
+ let unchanged = false;
1025
+ if (existed) {
1026
+ const existingContent = readFileSync(filePath, "utf-8");
1027
+ unchanged = existingContent === content;
1028
+ }
1029
+ if (!unchanged) {
1030
+ writeFileSync(filePath, content, "utf-8");
1031
+ }
1032
+ return {
1033
+ path: filePath,
1034
+ created: !existed,
1035
+ updated: existed && !unchanged,
1036
+ unchanged
1037
+ };
1038
+ }
1039
+ function writeJsonFile(filePath, data, indent = 2) {
1040
+ const content = JSON.stringify(data, null, indent) + "\n";
1041
+ return writeFile(filePath, content);
1042
+ }
1043
+ function batchWriteFiles(files) {
1044
+ const results = [];
1045
+ for (const { path, content } of files) {
1046
+ results.push(writeFile(path, content));
1047
+ }
1048
+ return {
1049
+ results,
1050
+ created: results.filter((r) => r.created).length,
1051
+ updated: results.filter((r) => r.updated).length,
1052
+ unchanged: results.filter((r) => r.unchanged).length,
1053
+ total: results.length
1054
+ };
1055
+ }
1056
+ function computeHash(content) {
1057
+ return createHash("sha256").update(content).digest("hex").slice(0, 16);
1058
+ }
1059
+ function checkFileDrift(filePath, expectedContent) {
1060
+ const expectedHash = computeHash(expectedContent);
1061
+ if (!existsSync(filePath)) {
1062
+ return {
1063
+ file: filePath,
1064
+ hasDrift: true,
1065
+ expectedHash,
1066
+ missing: true
1067
+ };
1068
+ }
1069
+ const actualContent = readFileSync(filePath, "utf-8");
1070
+ const actualHash = computeHash(actualContent);
1071
+ return {
1072
+ file: filePath,
1073
+ hasDrift: expectedHash !== actualHash,
1074
+ expectedHash,
1075
+ actualHash
1076
+ };
1077
+ }
1078
+ function checkBatchDrift(files) {
1079
+ return files.map(({ path, content }) => checkFileDrift(path, content));
1080
+ }
1081
+ async function findFiles(pattern, cwd = process.cwd()) {
1082
+ return glob(pattern, { cwd, absolute: true });
1083
+ }
1084
+ async function findTypeScriptFiles(srcDir) {
1085
+ return findFiles("**/*.ts", srcDir);
1086
+ }
1087
+ async function findDefinitionFiles(srcDir, subDir) {
1088
+ const pattern = join(srcDir, subDir, "**/*.ts");
1089
+ return glob(pattern, { absolute: true, ignore: ["**/*.d.ts", "**/index.ts"] });
1090
+ }
1091
+ function generateDocsPath(baseDir, category, filename) {
1092
+ return join(baseDir, category, filename);
1093
+ }
1094
+ function generateSpecsPath(baseDir, category, filename) {
1095
+ return join(baseDir, category, filename);
1096
+ }
1097
+ function createEmptyRegistry() {
1098
+ return {
1099
+ requirements: /* @__PURE__ */ new Map(),
1100
+ useCases: /* @__PURE__ */ new Map(),
1101
+ actors: /* @__PURE__ */ new Map(),
1102
+ components: /* @__PURE__ */ new Map(),
1103
+ entities: /* @__PURE__ */ new Map(),
1104
+ relations: /* @__PURE__ */ new Map(),
1105
+ rules: /* @__PURE__ */ new Map(),
1106
+ screens: /* @__PURE__ */ new Map(),
1107
+ transitions: /* @__PURE__ */ new Map(),
1108
+ forms: /* @__PURE__ */ new Map(),
1109
+ processFlows: /* @__PURE__ */ new Map(),
1110
+ glossaryTerms: /* @__PURE__ */ new Map(),
1111
+ apiRefs: /* @__PURE__ */ new Map(),
1112
+ tableRefs: /* @__PURE__ */ new Map(),
1113
+ testRefs: /* @__PURE__ */ new Map(),
1114
+ layers: /* @__PURE__ */ new Map(),
1115
+ boundaries: /* @__PURE__ */ new Map(),
1116
+ artifacts: /* @__PURE__ */ new Map()
1117
+ };
1118
+ }
1119
+ var globalRegistry2 = createEmptyRegistry();
1120
+ function getGlobalRegistry() {
1121
+ return globalRegistry2;
1122
+ }
1123
+ function resetGlobalRegistry() {
1124
+ globalRegistry2 = createEmptyRegistry();
1125
+ }
1126
+ function isModelInstance(value) {
1127
+ if (!value || typeof value !== "object") return false;
1128
+ const obj = value;
1129
+ return typeof obj.id === "string" && typeof obj.name === "string" && typeof obj.idPrefix === "string" && obj.schema !== void 0 && typeof obj.validate === "function" && typeof obj.lint === "function";
1130
+ }
1131
+ function registerExportedModels(module) {
1132
+ for (const [exportName, exportValue] of Object.entries(module)) {
1133
+ if (!exportValue || typeof exportValue !== "object") continue;
1134
+ if (isModelInstance(exportValue)) {
1135
+ registerModel(exportValue);
1136
+ continue;
1137
+ }
1138
+ if (Array.isArray(exportValue)) {
1139
+ registerArrayItems(exportName, exportValue);
1140
+ continue;
1141
+ }
1142
+ const value = exportValue;
1143
+ if (value.components && value.layers && value.boundaries && value.relations) {
1144
+ const arch = value;
1145
+ for (const comp of arch.components) {
1146
+ if (comp.id) globalRegistry2.components.set(comp.id, comp);
1147
+ }
1148
+ for (const layer of arch.layers) {
1149
+ if (layer.id) globalRegistry2.layers.set(layer.id, layer);
1150
+ }
1151
+ for (const boundary of arch.boundaries) {
1152
+ if (boundary.id) globalRegistry2.boundaries.set(boundary.id, boundary);
1153
+ }
1154
+ for (const rel of arch.relations) {
1155
+ if (rel.id) globalRegistry2.relations.set(rel.id, rel);
1156
+ }
1157
+ continue;
1158
+ }
1159
+ if (value.entities && value.relations && Array.isArray(value.entities)) {
1160
+ const concept = value;
1161
+ for (const entity of concept.entities) {
1162
+ if (entity.id) globalRegistry2.entities.set(entity.id, entity);
1163
+ }
1164
+ for (const rel of concept.relations) {
1165
+ if (rel.id) globalRegistry2.relations.set(rel.id, rel);
1166
+ }
1167
+ for (const rule of concept.rules || []) {
1168
+ if (rule.id) globalRegistry2.rules.set(rule.id, rule);
1169
+ }
1170
+ continue;
1171
+ }
1172
+ if (value.useCases && value.actors) {
1173
+ const ucModel = value;
1174
+ for (const uc of ucModel.useCases) {
1175
+ if (uc.id) globalRegistry2.useCases.set(uc.id, uc);
1176
+ }
1177
+ for (const actor of ucModel.actors) {
1178
+ if (actor.id) globalRegistry2.actors.set(actor.id, actor);
1179
+ }
1180
+ continue;
1181
+ }
1182
+ if (value.terms && Array.isArray(value.terms)) {
1183
+ const glossary = value;
1184
+ for (const term of glossary.terms) {
1185
+ if (term.id) globalRegistry2.glossaryTerms.set(term.id, term);
1186
+ }
1187
+ continue;
1188
+ }
1189
+ if (value.id && value.title && (value.kind || value.type || value.acceptance)) {
1190
+ globalRegistry2.requirements.set(value.id, value);
1191
+ continue;
1192
+ }
1193
+ if (value.id && value.name && value.attributes && Array.isArray(value.attributes)) {
1194
+ globalRegistry2.entities.set(value.id, value);
1195
+ continue;
1196
+ }
1197
+ if (value.id && value.name && value.actor && value.mainFlow) {
1198
+ globalRegistry2.useCases.set(value.id, value);
1199
+ continue;
1200
+ }
1201
+ if (value.id && value.name && value.type && ["human", "system", "external"].includes(value.type)) {
1202
+ globalRegistry2.actors.set(value.id, value);
1203
+ continue;
1204
+ }
1205
+ if (value.id && (value.term || value.abbreviation) && value.definition) {
1206
+ globalRegistry2.glossaryTerms.set(value.id, value);
1207
+ continue;
1208
+ }
1209
+ }
1210
+ }
1211
+ function registerArrayItems(exportName, items) {
1212
+ for (const item of items) {
1213
+ if (!item || typeof item !== "object") continue;
1214
+ const obj = item;
1215
+ if (!obj.id || typeof obj.id !== "string") continue;
1216
+ if (exportName.toLowerCase().includes("requirement") || exportName.toLowerCase().includes("constraints") || obj.type && obj.acceptanceCriteria) {
1217
+ globalRegistry2.requirements.set(obj.id, item);
1218
+ continue;
1219
+ }
1220
+ if (exportName.toLowerCase().includes("usecase") || obj.actor && obj.mainFlow) {
1221
+ globalRegistry2.useCases.set(obj.id, item);
1222
+ continue;
1223
+ }
1224
+ if (exportName.toLowerCase().includes("actor") || obj.type && ["human", "system", "external"].includes(obj.type) && !obj.mainFlow) {
1225
+ globalRegistry2.actors.set(obj.id, item);
1226
+ continue;
1227
+ }
1228
+ if (exportName.toLowerCase().includes("component") || exportName.toLowerCase().includes("container") || obj.type && ["person", "system", "container", "component"].includes(obj.type)) {
1229
+ globalRegistry2.components.set(obj.id, item);
1230
+ continue;
1231
+ }
1232
+ if (exportName.toLowerCase().includes("entit") || obj.name && obj.attributes && Array.isArray(obj.attributes)) {
1233
+ globalRegistry2.entities.set(obj.id, item);
1234
+ continue;
1235
+ }
1236
+ if (exportName.toLowerCase().includes("term") || exportName.toLowerCase().includes("acronym") || exportName.toLowerCase().includes("glossary") || obj.term && obj.definition) {
1237
+ globalRegistry2.glossaryTerms.set(obj.id, item);
1238
+ continue;
1239
+ }
1240
+ if (exportName.toLowerCase().includes("relation") || obj.from && obj.to && obj.label) {
1241
+ globalRegistry2.relations.set(obj.id, item);
1242
+ continue;
1243
+ }
1244
+ if (exportName.toLowerCase().includes("layer") || obj.name && typeof obj.order === "number") {
1245
+ globalRegistry2.layers.set(obj.id, item);
1246
+ continue;
1247
+ }
1248
+ if (exportName.toLowerCase().includes("boundar") || obj.type && ["system", "context", "container", "deployment"].includes(obj.type) && obj.description) {
1249
+ globalRegistry2.boundaries.set(obj.id, item);
1250
+ continue;
1251
+ }
1252
+ if (exportName.toLowerCase().includes("screen") || obj.type && ["page", "modal", "drawer", "panel", "wizard"].includes(obj.type) && obj.auth) {
1253
+ globalRegistry2.screens.set(obj.id, item);
1254
+ continue;
1255
+ }
1256
+ if (exportName.toLowerCase().includes("testref") || exportName.toLowerCase().includes("test-ref") || obj.source && obj.verifiesRequirements && Array.isArray(obj.verifiesRequirements)) {
1257
+ globalRegistry2.testRefs.set(obj.id, item);
1258
+ continue;
1259
+ }
1260
+ if (exportName.toLowerCase().includes("artifact") || obj.category && obj.location && obj.purpose && ["ssot", "human-readable", "machine-readable", "implementation"].includes(obj.category)) {
1261
+ globalRegistry2.artifacts.set(obj.id, item);
1262
+ continue;
1263
+ }
1264
+ }
1265
+ }
1266
+ async function loadModelsFromDirectory(dirPath, options = {}) {
1267
+ const loadedFiles = [];
1268
+ if (!existsSync(dirPath)) {
1269
+ return loadedFiles;
1270
+ }
1271
+ const entries = readdirSync(dirPath);
1272
+ for (const entry of entries) {
1273
+ const fullPath = join(dirPath, entry);
1274
+ const stat = statSync(fullPath);
1275
+ if (stat.isDirectory() && options.recursive) {
1276
+ const subFiles = await loadModelsFromDirectory(fullPath, options);
1277
+ loadedFiles.push(...subFiles);
1278
+ } else if (stat.isFile()) {
1279
+ const ext = extname(entry);
1280
+ if ([".ts", ".js", ".mts", ".mjs"].includes(ext) && !entry.endsWith(".d.ts")) {
1281
+ if (basename(entry, ext) === "index") {
1282
+ continue;
1283
+ }
1284
+ try {
1285
+ const module = await import(fullPath);
1286
+ loadedFiles.push(fullPath);
1287
+ registerExportedModels(module);
1288
+ } catch (error) {
1289
+ console.error(`Failed to load model from ${fullPath}:`, error);
1290
+ }
1291
+ }
1292
+ }
1293
+ }
1294
+ return loadedFiles;
1295
+ }
1296
+ async function loadAllModels(config, cwd = process.cwd()) {
1297
+ resetGlobalRegistry();
1298
+ const loadedFiles = [];
1299
+ const errors = [];
1300
+ const designDir = resolve(cwd, config.designDir || "design");
1301
+ const modelDirs = [
1302
+ { path: join(designDir, "requirements") },
1303
+ { path: join(designDir, "usecases") },
1304
+ { path: join(designDir, "architecture") },
1305
+ { path: join(designDir, "data-model") },
1306
+ { path: join(designDir, "screens") },
1307
+ { path: join(designDir, "flows") },
1308
+ { path: join(designDir, "glossary") },
1309
+ // Also load files directly under design/ by default (index.ts, etc.)
1310
+ { path: designDir }
1311
+ ];
1312
+ for (const { path: dirPath } of modelDirs) {
1313
+ if (!existsSync(dirPath)) {
1314
+ continue;
1315
+ }
1316
+ try {
1317
+ const files = await loadModelsFromDirectory(dirPath, { recursive: true });
1318
+ loadedFiles.push(...files);
1319
+ } catch (error) {
1320
+ errors.push({ file: dirPath, error });
1321
+ }
1322
+ }
1323
+ return {
1324
+ registry: getGlobalRegistry(),
1325
+ loadedFiles,
1326
+ errors
1327
+ };
1328
+ }
1329
+ function getRegistryStats(registry) {
1330
+ const stats = {};
1331
+ for (const [key, map] of Object.entries(registry)) {
1332
+ stats[key] = map.size;
1333
+ }
1334
+ return stats;
1335
+ }
1336
+ function getAllIds(registry) {
1337
+ const allIds = /* @__PURE__ */ new Set();
1338
+ for (const map of Object.values(registry)) {
1339
+ for (const id of map.keys()) {
1340
+ allIds.add(id);
1341
+ }
1342
+ }
1343
+ return allIds;
1344
+ }
1345
+ function findModelType(registry, id) {
1346
+ for (const [type, map] of Object.entries(registry)) {
1347
+ if (map.has(id)) {
1348
+ return type;
1349
+ }
1350
+ }
1351
+ return null;
1352
+ }
1353
+ function getModelById(registry, id) {
1354
+ const type = findModelType(registry, id);
1355
+ if (!type) return null;
1356
+ return {
1357
+ type,
1358
+ model: registry[type].get(id)
1359
+ };
1360
+ }
1361
+
1362
+ export { CheckErrorSchema, CheckResultSchema, CheckWarningSchema, ConcretizationSlotSchema, ID_PATTERNS, LinkSchema, LintIssueSchema, Model, PhaseSchema, PrioritySchema, RELATION_CONSTRAINTS, RELATION_TYPES, ReferenceDefinitionSchema, RelationSchema, RelationsFieldSchema, batchWriteFiles, checkBatchDrift, checkExternalSsot, checkFileDrift, checkReferenceIntegrity, clearModelRegistry, computeHash, createAPIRefId, createActorId, createBatchRefId, createBoundaryId, createComponentId, createDTORequirementId, createEmptyRegistry, createEntityId, createErrorCaseId, createEventRequirementId, createFormId, createIaCRefId, createLayerId, createLintRule, createMarkdownExporter, createMermaidExporter, createProcessFlowId, createRelationId, createRequirementId, createRuleId, createScreenId, createStateId, createTableRefId, createTermId, createTransitionId, createUIComponentId, createUseCaseId, defineConfig, defineModel, detectCycles, ensureDir, exportAllModels, exportMarkdown, exportMermaid, findConfigFile, findDefinitionFiles, findFiles, findModelType, findTypeScriptFiles, generateDocsPath, generateSpecsPath, getAllIds, getAllInstances, getAllModelDefinitions, getAllModelInstances, getAllModels, getExpectedOutputDirs, getGlobalModelRegistry, getGlobalRegistry, getLevelIndex, getModel, getModelById, getModelDefinition, getModelInstance, getModelRegistryStats, getOutputPaths, getRegistryStats, inferModelIdFromSpecId, lintAllModels, lintModel, loadAllModels, loadConfig, loadModelsFromDirectory, loadSpeckeeperConfig, registerModel, registerModelDefinition, registerModelDefinitions, registerModelInstance, resetGlobalRegistry, resetModelInstances, resetModelRegistry, resolveReference, validateConfig, validateOutputPaths, validateRelationLevel, writeFile, writeJsonFile };
1363
+ //# sourceMappingURL=index.js.map
1364
+ //# sourceMappingURL=index.js.map