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/README.md +273 -0
- package/bin/speckeeper.js +10 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +1110 -0
- package/dist/cli.js.map +1 -0
- package/dist/dsl/index.d.ts +2 -0
- package/dist/dsl/index.js +3 -0
- package/dist/dsl/index.js.map +1 -0
- package/dist/index.d.ts +1377 -0
- package/dist/index.js +1364 -0
- package/dist/index.js.map +1 -0
- package/dist/templates/init/design/_models/component.ts +70 -0
- package/dist/templates/init/design/_models/entity.ts +82 -0
- package/dist/templates/init/design/_models/index.ts +33 -0
- package/dist/templates/init/design/_models/requirement.ts +95 -0
- package/dist/templates/init/design/_models/term.ts +58 -0
- package/dist/templates/init/design/_models/usecase.ts +99 -0
- package/dist/templates/init/design/index.ts +8 -0
- package/dist/templates/init/design/requirements.ts +20 -0
- package/dist/templates/init/package.json.template +17 -0
- package/dist/templates/init/speckeeper.config.ts +11 -0
- package/dist/templates/init/tsconfig.json +14 -0
- package/package.json +80 -0
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
|