speckeeper 0.2.1 → 0.4.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 +13 -5
- package/dist/cli.js +109 -73
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +52 -40
- package/dist/index.js +39 -38
- package/dist/index.js.map +1 -1
- package/dist/templates/init/design/index.ts +4 -5
- package/dist/templates/init/design/requirements.ts +5 -4
- package/dist/templates/init/speckeeper.config.ts +3 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -69,21 +69,23 @@ npx speckeeper scaffold --source requirements.md
|
|
|
69
69
|
|
|
70
70
|
This generates:
|
|
71
71
|
- `design/_models/` — Model classes with Zod schemas, lint rules, and exporters derived from your flowchart
|
|
72
|
+
- `design/*.ts` — Spec data files using `defineSpecs()` for each model
|
|
73
|
+
- `design/index.ts` — Entry point that aggregates all spec modules via `mergeSpecs()`
|
|
72
74
|
- `design/_checkers/` — External checker skeletons for `implements` edges (e.g. OpenAPI, DDL)
|
|
73
|
-
- `design/_models/index.ts` — Re-exports and `allModels` array
|
|
74
|
-
- `speckeeper.config.ts` — Configuration wired to the generated models
|
|
75
75
|
|
|
76
76
|
See [Scaffold Mermaid Specification](./docs/scaffold-mermaid-spec.md) for the full input format and built-in node mappings.
|
|
77
77
|
|
|
78
78
|
### 3. Fill in your specifications
|
|
79
79
|
|
|
80
|
-
Edit files in `design/` to add your actual specification data:
|
|
80
|
+
Edit spec data files in `design/` to add your actual specification data. Each file uses `defineSpecs()` to pair Model instances with data:
|
|
81
81
|
|
|
82
82
|
```typescript
|
|
83
83
|
// design/requirements.ts
|
|
84
|
-
import
|
|
84
|
+
import { defineSpecs } from 'speckeeper';
|
|
85
|
+
import type { Requirement } from './_models/requirement';
|
|
86
|
+
import { FunctionalRequirementModel } from './_models/requirement';
|
|
85
87
|
|
|
86
|
-
|
|
88
|
+
const requirements: Requirement[] = [
|
|
87
89
|
{
|
|
88
90
|
id: 'FR-001',
|
|
89
91
|
name: 'User Authentication',
|
|
@@ -96,8 +98,14 @@ export const requirements: Requirement[] = [
|
|
|
96
98
|
],
|
|
97
99
|
},
|
|
98
100
|
];
|
|
101
|
+
|
|
102
|
+
export default defineSpecs(
|
|
103
|
+
[FunctionalRequirementModel.instance, requirements],
|
|
104
|
+
);
|
|
99
105
|
```
|
|
100
106
|
|
|
107
|
+
`design/index.ts` aggregates all spec files, and `speckeeper.config.ts` imports the result — no manual wiring needed beyond adding your spec file to `design/index.ts`.
|
|
108
|
+
|
|
101
109
|
### 4. Run validation
|
|
102
110
|
|
|
103
111
|
```bash
|
package/dist/cli.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from 'commander';
|
|
3
|
-
import
|
|
3
|
+
import { readFileSync, existsSync, writeFileSync, mkdirSync, readdirSync, statSync } from 'fs';
|
|
4
4
|
import { dirname, join, resolve } from 'path';
|
|
5
|
-
import {
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
import chalk4 from 'chalk';
|
|
6
7
|
import { parse } from 'yaml';
|
|
7
8
|
import 'crypto';
|
|
8
9
|
import 'glob';
|
|
9
10
|
import { z } from 'zod';
|
|
10
|
-
import { fileURLToPath } from 'url';
|
|
11
11
|
|
|
12
12
|
var defaultConfig = {
|
|
13
13
|
srcDir: "src",
|
|
@@ -123,32 +123,36 @@ var RelationSchema = z.object({
|
|
|
123
123
|
z.array(RelationSchema).optional();
|
|
124
124
|
|
|
125
125
|
// src/core/model.ts
|
|
126
|
-
var specStore = /* @__PURE__ */ new Map();
|
|
127
|
-
function getSpecStore() {
|
|
128
|
-
return specStore;
|
|
129
|
-
}
|
|
130
126
|
var modelRegistry = /* @__PURE__ */ new Map();
|
|
131
|
-
function registerModel(model) {
|
|
132
|
-
modelRegistry.set(model.id, model);
|
|
133
|
-
}
|
|
134
127
|
function getAllModels() {
|
|
135
128
|
return Array.from(modelRegistry.values());
|
|
136
129
|
}
|
|
137
|
-
function
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
130
|
+
function buildRegistryFromConfig(specs) {
|
|
131
|
+
const registry = {};
|
|
132
|
+
if (!specs) return registry;
|
|
133
|
+
for (const entry of specs) {
|
|
134
|
+
if (!registry[entry.model.id]) {
|
|
135
|
+
registry[entry.model.id] = /* @__PURE__ */ new Map();
|
|
136
|
+
}
|
|
137
|
+
const map = registry[entry.model.id];
|
|
138
|
+
for (const spec of entry.data) {
|
|
139
|
+
map.set(spec.id, spec);
|
|
141
140
|
}
|
|
142
141
|
}
|
|
142
|
+
return registry;
|
|
143
143
|
}
|
|
144
|
-
function
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
return Array.from(map.values());
|
|
144
|
+
function getSpecsFromConfig(specs, modelId) {
|
|
145
|
+
if (!specs) return [];
|
|
146
|
+
return specs.filter((e) => e.model.id === modelId).flatMap((e) => e.data);
|
|
148
147
|
}
|
|
149
|
-
function
|
|
150
|
-
|
|
151
|
-
|
|
148
|
+
function findModelTypeFromConfig(specs, specId) {
|
|
149
|
+
if (!specs) return null;
|
|
150
|
+
for (const entry of specs) {
|
|
151
|
+
for (const spec of entry.data) {
|
|
152
|
+
if (spec.id === specId) {
|
|
153
|
+
return entry.model.id;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
152
156
|
}
|
|
153
157
|
return null;
|
|
154
158
|
}
|
|
@@ -164,13 +168,11 @@ async function buildCommand(options) {
|
|
|
164
168
|
console.log(chalk4.gray(` Specs: ${config.specsDir}/`));
|
|
165
169
|
console.log("");
|
|
166
170
|
try {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
console.log("");
|
|
171
|
+
const models = config.models || [];
|
|
172
|
+
const specs = config.specs;
|
|
170
173
|
ensureDir(join(cwd, config.docsDir));
|
|
171
174
|
ensureDir(join(cwd, config.specsDir));
|
|
172
175
|
const files = [];
|
|
173
|
-
const models = getAllModels();
|
|
174
176
|
if (models.length === 0) {
|
|
175
177
|
console.log(chalk4.yellow(" No models registered. Add models to speckeeper.config.ts."));
|
|
176
178
|
return;
|
|
@@ -184,8 +186,8 @@ async function buildCommand(options) {
|
|
|
184
186
|
}
|
|
185
187
|
continue;
|
|
186
188
|
}
|
|
187
|
-
const
|
|
188
|
-
if (
|
|
189
|
+
const modelSpecs = getSpecsFromConfig(specs, model.id);
|
|
190
|
+
if (modelSpecs.length === 0) {
|
|
189
191
|
if (options.verbose) {
|
|
190
192
|
console.log(chalk4.gray(` ${model.name}: no specs found`));
|
|
191
193
|
}
|
|
@@ -195,7 +197,7 @@ async function buildCommand(options) {
|
|
|
195
197
|
const outputDir = exporter.outputDir ? join(cwd, config.docsDir, exporter.outputDir) : join(cwd, config.docsDir);
|
|
196
198
|
ensureDir(outputDir);
|
|
197
199
|
if (exporter.single) {
|
|
198
|
-
for (const spec of
|
|
200
|
+
for (const spec of modelSpecs) {
|
|
199
201
|
const content = exporter.single(spec);
|
|
200
202
|
const filename = model.getFilename(spec, exporter.format) || spec.id;
|
|
201
203
|
files.push({
|
|
@@ -205,7 +207,7 @@ async function buildCommand(options) {
|
|
|
205
207
|
}
|
|
206
208
|
}
|
|
207
209
|
if (exporter.index) {
|
|
208
|
-
const indexContent = exporter.index(
|
|
210
|
+
const indexContent = exporter.index(modelSpecs);
|
|
209
211
|
files.push({
|
|
210
212
|
path: join(outputDir, "index.md"),
|
|
211
213
|
content: indexContent
|
|
@@ -241,13 +243,12 @@ async function lintCommand(options) {
|
|
|
241
243
|
}
|
|
242
244
|
console.log("");
|
|
243
245
|
try {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
const models = getAllModels();
|
|
246
|
+
const models = config.models || [];
|
|
247
|
+
const specs = config.specs;
|
|
247
248
|
console.log(chalk4.gray(` Loaded: ${models.length} models`));
|
|
248
249
|
console.log("");
|
|
249
250
|
console.log(chalk4.blue(" Running lint checks..."));
|
|
250
|
-
const result = runModelLint(options);
|
|
251
|
+
const result = runModelLint(models, specs, options);
|
|
251
252
|
console.log("");
|
|
252
253
|
outputLintResults(result, options);
|
|
253
254
|
if (result.errors > 0) {
|
|
@@ -258,13 +259,12 @@ async function lintCommand(options) {
|
|
|
258
259
|
process.exit(1);
|
|
259
260
|
}
|
|
260
261
|
}
|
|
261
|
-
function runModelLint(options) {
|
|
262
|
+
function runModelLint(models, specs, options) {
|
|
262
263
|
const issues = [];
|
|
263
|
-
const models = getAllModels();
|
|
264
264
|
for (const model of models) {
|
|
265
|
-
const
|
|
266
|
-
if (
|
|
267
|
-
const lintResults = model.lintAll(
|
|
265
|
+
const modelSpecs = getSpecsFromConfig(specs, model.id);
|
|
266
|
+
if (modelSpecs.length === 0) continue;
|
|
267
|
+
const lintResults = model.lintAll(modelSpecs);
|
|
268
268
|
for (const result of lintResults) {
|
|
269
269
|
issues.push({
|
|
270
270
|
rule: result.ruleId,
|
|
@@ -323,18 +323,17 @@ async function driftCommand(options) {
|
|
|
323
323
|
console.log(chalk4.gray(` Docs: ${config.docsDir}/`));
|
|
324
324
|
console.log("");
|
|
325
325
|
try {
|
|
326
|
-
|
|
327
|
-
|
|
326
|
+
const models = config.models || [];
|
|
327
|
+
const specs = config.specs;
|
|
328
328
|
const results = [];
|
|
329
|
-
const models = getAllModels();
|
|
330
329
|
for (const model of models) {
|
|
331
|
-
const
|
|
332
|
-
if (
|
|
330
|
+
const modelSpecs = getSpecsFromConfig(specs, model.id);
|
|
331
|
+
if (modelSpecs.length === 0) continue;
|
|
333
332
|
for (const exporter of model.getExporters()) {
|
|
334
333
|
if (exporter.format !== "markdown") continue;
|
|
335
334
|
const outputDir = exporter.outputDir ? join(cwd, config.docsDir, exporter.outputDir) : join(cwd, config.docsDir);
|
|
336
335
|
if (exporter.single) {
|
|
337
|
-
for (const spec of
|
|
336
|
+
for (const spec of modelSpecs) {
|
|
338
337
|
const filename = model.getFilename(spec, "markdown") || spec.id;
|
|
339
338
|
const filePath = join(outputDir, `${filename}.md`);
|
|
340
339
|
if (!existsSync(filePath)) {
|
|
@@ -355,7 +354,7 @@ async function driftCommand(options) {
|
|
|
355
354
|
if (!existsSync(indexPath)) {
|
|
356
355
|
results.push({ file: indexPath, status: "missing" });
|
|
357
356
|
} else {
|
|
358
|
-
const expected = exporter.index(
|
|
357
|
+
const expected = exporter.index(modelSpecs);
|
|
359
358
|
const actual = readFileSync(indexPath, "utf-8");
|
|
360
359
|
if (normalizeContent(expected) !== normalizeContent(actual)) {
|
|
361
360
|
results.push({ file: indexPath, status: "drifted" });
|
|
@@ -414,17 +413,16 @@ async function checkCommand(type, options) {
|
|
|
414
413
|
console.log(chalk4.gray(` Type: ${checkType}`));
|
|
415
414
|
console.log("");
|
|
416
415
|
try {
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
const results = [];
|
|
420
|
-
const models = getAllModels();
|
|
416
|
+
const models = config.models || [];
|
|
417
|
+
const specs = config.specs;
|
|
421
418
|
if (options.verbose) {
|
|
422
419
|
console.log(chalk4.gray(` Registered models: ${models.map((m) => m.id).join(", ")}`));
|
|
423
420
|
}
|
|
421
|
+
const results = [];
|
|
424
422
|
for (const model of models) {
|
|
425
|
-
const
|
|
426
|
-
if (
|
|
427
|
-
for (const spec of
|
|
423
|
+
const modelSpecs = getSpecsFromConfig(specs, model.id);
|
|
424
|
+
if (modelSpecs.length === 0) continue;
|
|
425
|
+
for (const spec of modelSpecs) {
|
|
428
426
|
const sourcePath = model.getExternalSourcePath(spec);
|
|
429
427
|
if (!sourcePath) continue;
|
|
430
428
|
const fullPath = join(cwd, sourcePath);
|
|
@@ -458,7 +456,7 @@ async function checkCommand(type, options) {
|
|
|
458
456
|
if (options.coverage) {
|
|
459
457
|
console.log("");
|
|
460
458
|
console.log(chalk4.blue(" Coverage checks..."));
|
|
461
|
-
const coverageResults = runAllCoverageChecks();
|
|
459
|
+
const coverageResults = runAllCoverageChecks(models, specs);
|
|
462
460
|
if (coverageResults.length > 0) {
|
|
463
461
|
outputAllCoverageResults(coverageResults);
|
|
464
462
|
for (const check of coverageResults) {
|
|
@@ -466,7 +464,6 @@ async function checkCommand(type, options) {
|
|
|
466
464
|
results.push({
|
|
467
465
|
type: `coverage-${check.modelId}`,
|
|
468
466
|
success: check.result.coveragePercent >= 80,
|
|
469
|
-
// fail if below 80%
|
|
470
467
|
issues: check.result.uncoveredItems.slice(0, 10).map((item) => ({
|
|
471
468
|
severity: "warning",
|
|
472
469
|
message: `[${check.modelName}\u2192${check.targetModel}] '${item.id}'${item.sourceId ? ` (${item.sourceId})` : ""} not covered`,
|
|
@@ -528,15 +525,14 @@ function outputCheckResults(results) {
|
|
|
528
525
|
console.log("");
|
|
529
526
|
console.log(chalk4.gray(` Summary: ${totalErrors} errors, ${totalWarnings} warnings`));
|
|
530
527
|
}
|
|
531
|
-
function runAllCoverageChecks() {
|
|
532
|
-
const models = getAllModels();
|
|
528
|
+
function runAllCoverageChecks(models, specs) {
|
|
533
529
|
const results = [];
|
|
534
|
-
const registry =
|
|
530
|
+
const registry = buildRegistryFromConfig(specs);
|
|
535
531
|
for (const model of models) {
|
|
536
532
|
const checker = model.getCoverageChecker();
|
|
537
533
|
if (checker) {
|
|
538
|
-
const
|
|
539
|
-
const result = model.checkCoverage(
|
|
534
|
+
const modelSpecs = getSpecsFromConfig(specs, model.id);
|
|
535
|
+
const result = model.checkCoverage(modelSpecs, registry);
|
|
540
536
|
if (result) {
|
|
541
537
|
results.push({
|
|
542
538
|
modelId: model.id,
|
|
@@ -652,28 +648,27 @@ async function impactCommand(targetId, options) {
|
|
|
652
648
|
console.log(chalk4.gray(` Depth: ${maxDepth}`));
|
|
653
649
|
console.log("");
|
|
654
650
|
try {
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
const
|
|
658
|
-
const targetType = findModelTypeBySpecId(targetId);
|
|
651
|
+
const specs = config.specs;
|
|
652
|
+
const registry = buildRegistryFromConfig(specs);
|
|
653
|
+
const targetType = findModelTypeFromConfig(specs, targetId);
|
|
659
654
|
if (!targetType) {
|
|
660
655
|
console.error(chalk4.red(` Error: Target '${targetId}' not found`));
|
|
661
656
|
process.exit(1);
|
|
662
657
|
}
|
|
663
658
|
console.log(chalk4.blue(" Analyzing impact..."));
|
|
664
|
-
const result = analyzeImpact(
|
|
659
|
+
const result = analyzeImpact(registry, targetId, targetType, maxDepth);
|
|
665
660
|
outputImpactResults(result, options);
|
|
666
661
|
} catch (error) {
|
|
667
662
|
console.error(chalk4.red("Impact analysis failed:"), error);
|
|
668
663
|
process.exit(1);
|
|
669
664
|
}
|
|
670
665
|
}
|
|
671
|
-
function analyzeImpact(
|
|
666
|
+
function analyzeImpact(registry, targetId, targetType, maxDepth) {
|
|
672
667
|
const impactedNodes = [];
|
|
673
668
|
const visited = /* @__PURE__ */ new Set([targetId]);
|
|
674
669
|
function findReferences(id, depth) {
|
|
675
670
|
if (depth > maxDepth) return;
|
|
676
|
-
for (const [type, map] of
|
|
671
|
+
for (const [type, map] of Object.entries(registry)) {
|
|
677
672
|
for (const [itemId, item] of map) {
|
|
678
673
|
if (visited.has(itemId)) continue;
|
|
679
674
|
const itemStr = JSON.stringify(item);
|
|
@@ -772,14 +767,14 @@ async function runInit(options = {}) {
|
|
|
772
767
|
console.log(" Next steps:");
|
|
773
768
|
if (packageJsonCreated) {
|
|
774
769
|
console.log(chalk4.gray(" 1. Run `npm install` to install dependencies"));
|
|
775
|
-
console.log(chalk4.gray(" 2.
|
|
776
|
-
console.log(chalk4.gray(" 3.
|
|
770
|
+
console.log(chalk4.gray(" 2. Add spec data in design/*.ts using defineSpecs()"));
|
|
771
|
+
console.log(chalk4.gray(" 3. Import new spec files in design/index.ts"));
|
|
777
772
|
console.log(chalk4.gray(" 4. Run `npx speckeeper lint` to validate"));
|
|
778
773
|
} else {
|
|
779
774
|
console.log(chalk4.gray(" 1. Add speckeeper and zod to your package.json dependencies"));
|
|
780
775
|
console.log(chalk4.gray(' 2. Ensure "type": "module" is set in package.json'));
|
|
781
|
-
console.log(chalk4.gray(" 3.
|
|
782
|
-
console.log(chalk4.gray(" 4.
|
|
776
|
+
console.log(chalk4.gray(" 3. Add spec data in design/*.ts using defineSpecs()"));
|
|
777
|
+
console.log(chalk4.gray(" 4. Import new spec files in design/index.ts"));
|
|
783
778
|
console.log(chalk4.gray(" 5. Run `npx speckeeper lint` to validate"));
|
|
784
779
|
}
|
|
785
780
|
}
|
|
@@ -2271,14 +2266,17 @@ function generateSpecDataFile(node) {
|
|
|
2271
2266
|
*
|
|
2272
2267
|
* Generated by speckeeper scaffold \u2014 add your specs below.
|
|
2273
2268
|
*/
|
|
2269
|
+
import { defineSpecs } from 'speckeeper';
|
|
2274
2270
|
import type { ${typeName} } from './_models/${fileName}';
|
|
2275
2271
|
import { ${className} } from './_models/${fileName}';
|
|
2276
2272
|
|
|
2277
|
-
|
|
2273
|
+
const ${varName}: ${typeName}[] = [
|
|
2278
2274
|
// { id: '${templateInfo.defaultIdPrefix}-001', name: '...', description: '...' },
|
|
2279
2275
|
];
|
|
2280
2276
|
|
|
2281
|
-
|
|
2277
|
+
export default defineSpecs(
|
|
2278
|
+
[${className}.instance, ${varName}],
|
|
2279
|
+
);
|
|
2282
2280
|
`;
|
|
2283
2281
|
return {
|
|
2284
2282
|
relativePath: `${fileName}.ts`,
|
|
@@ -2288,6 +2286,34 @@ ${className}.instance.register(${varName});
|
|
|
2288
2286
|
function toCamelCase3(pascalCase) {
|
|
2289
2287
|
return pascalCase.charAt(0).toLowerCase() + pascalCase.slice(1);
|
|
2290
2288
|
}
|
|
2289
|
+
function generateDesignIndex(speckeeperNodes) {
|
|
2290
|
+
const generated = /* @__PURE__ */ new Set();
|
|
2291
|
+
const specFiles = [];
|
|
2292
|
+
for (const node of speckeeperNodes) {
|
|
2293
|
+
const templateInfo = resolveModelTemplate(node.id);
|
|
2294
|
+
const key = templateInfo.templateName === "base" ? `base:${node.id}` : templateInfo.templateName;
|
|
2295
|
+
if (generated.has(key)) continue;
|
|
2296
|
+
generated.add(key);
|
|
2297
|
+
specFiles.push({
|
|
2298
|
+
varName: toCamelCase3(templateInfo.modelName),
|
|
2299
|
+
fileName: templateInfo.fileName
|
|
2300
|
+
});
|
|
2301
|
+
}
|
|
2302
|
+
const imports = specFiles.map((f) => `import ${f.varName} from './${f.fileName}';`).join("\n");
|
|
2303
|
+
const args = specFiles.map((f) => f.varName).join(", ");
|
|
2304
|
+
const content = `/**
|
|
2305
|
+
* Design entry point \u2014 generated by speckeeper scaffold
|
|
2306
|
+
*/
|
|
2307
|
+
import { mergeSpecs } from 'speckeeper';
|
|
2308
|
+
${imports}
|
|
2309
|
+
|
|
2310
|
+
export default mergeSpecs(${args});
|
|
2311
|
+
`;
|
|
2312
|
+
return {
|
|
2313
|
+
relativePath: "index.ts",
|
|
2314
|
+
content
|
|
2315
|
+
};
|
|
2316
|
+
}
|
|
2291
2317
|
function generateAllModelFiles(speckeeperNodes, resolvedEdges) {
|
|
2292
2318
|
const files = [];
|
|
2293
2319
|
const generated = /* @__PURE__ */ new Set();
|
|
@@ -2301,6 +2327,7 @@ function generateAllModelFiles(speckeeperNodes, resolvedEdges) {
|
|
|
2301
2327
|
files.push(generateModelFile(node));
|
|
2302
2328
|
files.push(generateSpecDataFile(node));
|
|
2303
2329
|
}
|
|
2330
|
+
files.push(generateDesignIndex(speckeeperNodes));
|
|
2304
2331
|
return files;
|
|
2305
2332
|
}
|
|
2306
2333
|
|
|
@@ -2499,8 +2526,17 @@ function printDiagnostics(diagnostics) {
|
|
|
2499
2526
|
}
|
|
2500
2527
|
|
|
2501
2528
|
// src/cli/index.ts
|
|
2529
|
+
function getVersion() {
|
|
2530
|
+
try {
|
|
2531
|
+
const __dirname2 = dirname(fileURLToPath(import.meta.url));
|
|
2532
|
+
const pkg = JSON.parse(readFileSync(join(__dirname2, "..", "package.json"), "utf-8"));
|
|
2533
|
+
return pkg.version;
|
|
2534
|
+
} catch {
|
|
2535
|
+
return "0.0.0";
|
|
2536
|
+
}
|
|
2537
|
+
}
|
|
2502
2538
|
var program = new Command();
|
|
2503
|
-
program.name("speckeeper").description("Requirements and design management framework with TypeScript DSL").version(
|
|
2539
|
+
program.name("speckeeper").description("Requirements and design management framework with TypeScript DSL").version(getVersion());
|
|
2504
2540
|
program.command("build").description("Generate docs/ and specs/ from TypeScript models").option("-c, --config <path>", "Path to config file").option("-o, --output <path>", "Base output directory path", ".").option("-f, --format <format>", "Output format: markdown, json, both", "both").option("-w, --watch", "Watch for changes and auto-regenerate").option("-v, --verbose", "Show detailed output").action(buildCommand);
|
|
2505
2541
|
program.command("lint").description("Check design integrity (ID duplicates, references, layer violations, etc.)").option("-c, --config <path>", "Path to config file").option("-p, --phase <phase>", "Phase gate to check against (REQ, HLD, LLD, OPS)").option("-s, --strict", "Treat warnings as errors").option("--fix", "Attempt to fix auto-fixable issues").option("-f, --format <format>", "Output format: text, json, github", "text").action(lintCommand);
|
|
2506
2542
|
program.command("drift").description("Check if generated files have been manually edited").option("-c, --config <path>", "Path to config file").option("-u, --update", "Auto-update if differences are found").option("-f, --format <format>", "Output format: text, json, diff", "text").action(driftCommand);
|