speckeeper 0.2.1 → 0.3.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 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 type { Requirement } from 'speckeeper';
84
+ import { defineSpecs } from 'speckeeper';
85
+ import type { Requirement } from './_models/requirement';
86
+ import { FunctionalRequirementModel } from './_models/requirement';
85
87
 
86
- export const requirements: Requirement[] = [
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
@@ -152,6 +152,12 @@ function findModelTypeBySpecId(specId) {
152
152
  }
153
153
  return null;
154
154
  }
155
+ function registerSpecsFromConfig(specs) {
156
+ if (!specs) return;
157
+ for (const entry of specs) {
158
+ entry.model.register(entry.data);
159
+ }
160
+ }
155
161
 
156
162
  // src/cli/build.ts
157
163
  async function buildCommand(options) {
@@ -166,6 +172,7 @@ async function buildCommand(options) {
166
172
  try {
167
173
  console.log(chalk4.blue(" Loading models..."));
168
174
  registerModelsFromConfig(config.models || []);
175
+ registerSpecsFromConfig(config.specs);
169
176
  console.log("");
170
177
  ensureDir(join(cwd, config.docsDir));
171
178
  ensureDir(join(cwd, config.specsDir));
@@ -243,6 +250,7 @@ async function lintCommand(options) {
243
250
  try {
244
251
  console.log(chalk4.blue(" Loading models..."));
245
252
  registerModelsFromConfig(config.models || []);
253
+ registerSpecsFromConfig(config.specs);
246
254
  const models = getAllModels();
247
255
  console.log(chalk4.gray(` Loaded: ${models.length} models`));
248
256
  console.log("");
@@ -325,6 +333,7 @@ async function driftCommand(options) {
325
333
  try {
326
334
  console.log(chalk4.blue(" Loading models..."));
327
335
  registerModelsFromConfig(config.models || []);
336
+ registerSpecsFromConfig(config.specs);
328
337
  const results = [];
329
338
  const models = getAllModels();
330
339
  for (const model of models) {
@@ -416,6 +425,7 @@ async function checkCommand(type, options) {
416
425
  try {
417
426
  console.log(chalk4.blue(" Loading models..."));
418
427
  registerModelsFromConfig(config.models || []);
428
+ registerSpecsFromConfig(config.specs);
419
429
  const results = [];
420
430
  const models = getAllModels();
421
431
  if (options.verbose) {
@@ -654,6 +664,7 @@ async function impactCommand(targetId, options) {
654
664
  try {
655
665
  console.log(chalk4.blue(" Loading models..."));
656
666
  registerModelsFromConfig(config.models || []);
667
+ registerSpecsFromConfig(config.specs);
657
668
  const store = getSpecStore();
658
669
  const targetType = findModelTypeBySpecId(targetId);
659
670
  if (!targetType) {
@@ -772,14 +783,14 @@ async function runInit(options = {}) {
772
783
  console.log(" Next steps:");
773
784
  if (packageJsonCreated) {
774
785
  console.log(chalk4.gray(" 1. Run `npm install` to install dependencies"));
775
- console.log(chalk4.gray(" 2. Edit design/_models/ to customize your models"));
776
- console.log(chalk4.gray(" 3. Add specifications in design/"));
786
+ console.log(chalk4.gray(" 2. Add spec data in design/*.ts using defineSpecs()"));
787
+ console.log(chalk4.gray(" 3. Import new spec files in design/index.ts"));
777
788
  console.log(chalk4.gray(" 4. Run `npx speckeeper lint` to validate"));
778
789
  } else {
779
790
  console.log(chalk4.gray(" 1. Add speckeeper and zod to your package.json dependencies"));
780
791
  console.log(chalk4.gray(' 2. Ensure "type": "module" is set in package.json'));
781
- console.log(chalk4.gray(" 3. Edit design/_models/ to customize your models"));
782
- console.log(chalk4.gray(" 4. Add specifications in design/"));
792
+ console.log(chalk4.gray(" 3. Add spec data in design/*.ts using defineSpecs()"));
793
+ console.log(chalk4.gray(" 4. Import new spec files in design/index.ts"));
783
794
  console.log(chalk4.gray(" 5. Run `npx speckeeper lint` to validate"));
784
795
  }
785
796
  }
@@ -2271,14 +2282,17 @@ function generateSpecDataFile(node) {
2271
2282
  *
2272
2283
  * Generated by speckeeper scaffold \u2014 add your specs below.
2273
2284
  */
2285
+ import { defineSpecs } from 'speckeeper';
2274
2286
  import type { ${typeName} } from './_models/${fileName}';
2275
2287
  import { ${className} } from './_models/${fileName}';
2276
2288
 
2277
- export const ${varName}: ${typeName}[] = [
2289
+ const ${varName}: ${typeName}[] = [
2278
2290
  // { id: '${templateInfo.defaultIdPrefix}-001', name: '...', description: '...' },
2279
2291
  ];
2280
2292
 
2281
- ${className}.instance.register(${varName});
2293
+ export default defineSpecs(
2294
+ [${className}.instance, ${varName}],
2295
+ );
2282
2296
  `;
2283
2297
  return {
2284
2298
  relativePath: `${fileName}.ts`,
@@ -2288,6 +2302,34 @@ ${className}.instance.register(${varName});
2288
2302
  function toCamelCase3(pascalCase) {
2289
2303
  return pascalCase.charAt(0).toLowerCase() + pascalCase.slice(1);
2290
2304
  }
2305
+ function generateDesignIndex(speckeeperNodes) {
2306
+ const generated = /* @__PURE__ */ new Set();
2307
+ const specFiles = [];
2308
+ for (const node of speckeeperNodes) {
2309
+ const templateInfo = resolveModelTemplate(node.id);
2310
+ const key = templateInfo.templateName === "base" ? `base:${node.id}` : templateInfo.templateName;
2311
+ if (generated.has(key)) continue;
2312
+ generated.add(key);
2313
+ specFiles.push({
2314
+ varName: toCamelCase3(templateInfo.modelName),
2315
+ fileName: templateInfo.fileName
2316
+ });
2317
+ }
2318
+ const imports = specFiles.map((f) => `import ${f.varName} from './${f.fileName}';`).join("\n");
2319
+ const args = specFiles.map((f) => f.varName).join(", ");
2320
+ const content = `/**
2321
+ * Design entry point \u2014 generated by speckeeper scaffold
2322
+ */
2323
+ import { mergeSpecs } from 'speckeeper';
2324
+ ${imports}
2325
+
2326
+ export default mergeSpecs(${args});
2327
+ `;
2328
+ return {
2329
+ relativePath: "index.ts",
2330
+ content
2331
+ };
2332
+ }
2291
2333
  function generateAllModelFiles(speckeeperNodes, resolvedEdges) {
2292
2334
  const files = [];
2293
2335
  const generated = /* @__PURE__ */ new Set();
@@ -2301,6 +2343,7 @@ function generateAllModelFiles(speckeeperNodes, resolvedEdges) {
2301
2343
  files.push(generateModelFile(node));
2302
2344
  files.push(generateSpecDataFile(node));
2303
2345
  }
2346
+ files.push(generateDesignIndex(speckeeperNodes));
2304
2347
  return files;
2305
2348
  }
2306
2349