speckeeper 0.8.0 → 0.8.1

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
@@ -90,10 +90,9 @@ npx speckeeper scaffold --source requirements.md
90
90
  ```
91
91
 
92
92
  This generates:
93
- - `design/_models/` — Model classes with base schema (id, name, description, relations) and core factory imports. Customise after generation.
93
+ - `design/_models/` — Model classes with base schema, lint rules, and `annotationChecker` bindings derived from `implements`/`verifiedBy` edges
94
94
  - `design/*.ts` — Spec data files using `defineSpecs()`
95
95
  - `design/index.ts` — Entry point via `mergeSpecs()`
96
- - Checker bindings from `implements`/`verifiedBy` edges are added as guidance comments
97
96
 
98
97
  See [Scaffold Mermaid Specification](./docs/scaffold-mermaid-spec.md) for the full input format.
99
98
 
@@ -152,7 +151,7 @@ npx speckeeper impact FR-001
152
151
  | `speckeeper check` | Verify consistency with external SSOT |
153
152
  | `speckeeper check test --coverage` | Verify test coverage for requirements |
154
153
  | `speckeeper scaffold` | Generate model skeletons from a mermaid flowchart |
155
- | `speckeeper drift` | Detect manual edits to generated `specs/` files |
154
+ | `speckeeper drift` | Detect manual edits to generated `docs/` files |
156
155
  | `speckeeper impact <id>` | Analyze change impact for a specific element |
157
156
 
158
157
  **Note**: `speckeeper build` generates machine-readable `specs/` output. For human-readable docs (`docs/`), use [embedoc](https://www.npmjs.com/package/embedoc) or similar tools with the model rendering API.
@@ -184,25 +183,77 @@ Checks include:
184
183
 
185
184
  ### External SSOT Validation (check)
186
185
 
187
- Validate your specifications against actual implementation artifacts. speckeeper provides built-in checker factories via `speckeeper/dsl`:
186
+ Validate your specifications against actual implementation artifacts. speckeeper scans source and test files for **annotation comments** (`@verifies`, `@implements`, `@traces`) to automatically detect which specs are covered — no manual relation wiring needed.
188
187
 
189
- | Factory | Target | Validates |
190
- |---------|--------|-----------|
191
- | `testChecker()` | Test code | Test file existence + spec ID references in describe/it/test blocks |
192
- | `externalOpenAPIChecker()` | OpenAPI spec | Spec ID existence (operationId, path, schema, x-spec-id), HTTP method, parameter/response property names and types |
193
- | `externalSqlSchemaChecker()` | SQL schema | Table existence, column existence, type containment (DDL type must be equal or wider than spec type) |
194
- | `relationCoverage()` | Cross-model | Coverage of a target model via relations |
188
+ #### Annotation-based auto-detection
195
189
 
196
- Assign a checker to a model's `externalChecker` property. Scaffold emits guidance comments showing which factory to use based on your `implements` / `verifiedBy` edges.
190
+ Add annotations to your source and test files:
191
+
192
+ ```typescript
193
+ // tests/unit/auth.test.ts
194
+ // @verifies FR-001, FR-001-01
195
+ describe('User Authentication', () => { ... });
196
+ ```
197
+
198
+ ```typescript
199
+ // src/auth/handler.ts
200
+ // @implements FR-001
201
+ export class AuthHandler { ... }
202
+ ```
203
+
204
+ speckeeper scans for these annotations and automatically links specs to their implementation and tests. Annotations work in any comment style (`//`, `#`, `--`, `/* */`, `<!-- -->`).
205
+
206
+ #### Artifact configuration
207
+
208
+ Define scan targets per artifact class in your config:
209
+
210
+ ```typescript
211
+ // speckeeper.config.ts
212
+ export default defineConfig({
213
+ // ...
214
+ artifacts: {
215
+ test: {
216
+ globs: ['test/**/*.test.ts', 'tests/**/*.test.ts'],
217
+ contentPatterns: [/@verifies\s+([\w-]+(?:[,\s]+[\w-]+)*)/],
218
+ },
219
+ typescript: {
220
+ globs: ['src/**/*.ts'],
221
+ exclude: ['src/**/*.test.ts', 'src/**/*.d.ts'],
222
+ contentPatterns: [/@implements\s+([\w-]+(?:[,\s]+[\w-]+)*)/],
223
+ },
224
+ openapi: {
225
+ globs: ['api/openapi.yaml'],
226
+ },
227
+ },
228
+ });
229
+ ```
230
+
231
+ Scaffold auto-generates this config based on your Mermaid flowchart's external nodes and their artifact classes.
232
+
233
+ #### Checker factories
234
+
235
+ | Factory | Type | Validates |
236
+ |---------|------|-----------|
237
+ | `annotationChecker()` | Generic | Scans files for `@verifies`/`@implements`/`@traces` annotations matching spec IDs |
238
+ | `annotationCoverage()` | Coverage | Computes coverage from annotation scan results (no manual relations needed) |
239
+ | `externalOpenAPIChecker()` | Specialized | OpenAPI spec: operationId, path, schema, x-spec-id, HTTP method, parameter/response types |
240
+ | `externalSqlSchemaChecker()` | Specialized | SQL schema: table existence, column existence, type containment |
241
+
242
+ `annotationChecker()` is the generic checker. Specialized checkers (`externalOpenAPIChecker`, `externalSqlSchemaChecker`) extend it with source-level parsing for deeper validation. Scaffold generates the appropriate checker binding based on your artifact classes.
197
243
 
198
244
  ```typescript
199
245
  // design/_models/requirement.ts
200
- import { testChecker } from 'speckeeper/dsl';
246
+ import { annotationChecker } from 'speckeeper/dsl';
201
247
 
202
248
  class RequirementModel extends Model<typeof RequirementSchema> {
203
249
  // ... schema, lintRules, etc.
204
250
 
205
- protected externalChecker = testChecker<Requirement>();
251
+ protected externalChecker = annotationChecker<Requirement>({
252
+ checks: [
253
+ { artifact: 'test', relationType: 'verifiedBy' },
254
+ { artifact: 'typescript', relationType: 'implements' },
255
+ ],
256
+ });
206
257
  }
207
258
  ```
208
259
 
@@ -214,10 +265,14 @@ speckeeper check
214
265
  Design: design/
215
266
  Type: test
216
267
 
268
+ Scanning artifacts...
269
+ test: 12 @verifies annotations in 8 files
270
+ typescript: 5 @implements annotations in 4 files
271
+
217
272
  ✓ All checks passed
218
273
 
219
- Coverage: Requirement → UseCase
220
- Coverage: 100% (7/7 use cases covered)
274
+ Coverage: Requirement (verifiedBy → test)
275
+ Coverage: 100% (7/7 requirements verified)
221
276
  ```
222
277
 
223
278
  You can also implement custom checkers for any external source by defining an `ExternalChecker<T>` directly. See [Model Definition Guide](./docs/model-guide.md) for details.
@@ -299,7 +354,7 @@ class RunbookModel extends Model<typeof RunbookSchema> {
299
354
  }
300
355
  ```
301
356
 
302
- Core DSL factories (`speckeeper/dsl`) include `requireField`, `arrayMinLength`, `idFormat`, `childIdFormat`, `markdownExporter`, `testChecker`, `externalOpenAPIChecker`, `externalSqlSchemaChecker`, `relationCoverage`, and `baseSpecSchema`.
357
+ Core DSL factories (`speckeeper/dsl`) include `requireField`, `arrayMinLength`, `idFormat`, `childIdFormat`, `markdownExporter`, `annotationChecker`, `annotationCoverage`, `externalOpenAPIChecker`, `externalSqlSchemaChecker`, `relationCoverage`, and `baseSpecSchema`.
303
358
 
304
359
  ## Documentation
305
360
 
package/dist/cli.js CHANGED
@@ -196,7 +196,9 @@ async function buildCommand(options) {
196
196
  }
197
197
  for (const exporter of exporters) {
198
198
  const outputDir = exporter.outputDir ? join(cwd, config.docsDir, exporter.outputDir) : join(cwd, config.docsDir);
199
- ensureDir(outputDir);
199
+ if (exporter.single) {
200
+ ensureDir(outputDir);
201
+ }
200
202
  if (exporter.single) {
201
203
  for (const spec of modelSpecs) {
202
204
  const content = exporter.single(spec);
@@ -209,8 +211,12 @@ async function buildCommand(options) {
209
211
  }
210
212
  if (exporter.index) {
211
213
  const indexContent = exporter.index(modelSpecs);
214
+ const indexPath = exporter.outputFile ? join(cwd, config.docsDir, exporter.outputFile) : join(outputDir, "index.md");
215
+ if (!exporter.outputFile) {
216
+ ensureDir(outputDir);
217
+ }
212
218
  files.push({
213
- path: join(outputDir, "index.md"),
219
+ path: indexPath,
214
220
  content: indexContent
215
221
  });
216
222
  }
@@ -351,7 +357,7 @@ async function driftCommand(options) {
351
357
  }
352
358
  }
353
359
  if (exporter.index) {
354
- const indexPath = join(outputDir, "index.md");
360
+ const indexPath = exporter.outputFile ? join(cwd, config.docsDir, exporter.outputFile) : join(outputDir, "index.md");
355
361
  if (!existsSync(indexPath)) {
356
362
  results.push({ file: indexPath, status: "missing" });
357
363
  } else {
@@ -1648,7 +1654,7 @@ var program = new Command();
1648
1654
  program.name("speckeeper").description("Requirements and design management framework with TypeScript DSL").version(getVersion());
1649
1655
  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);
1650
1656
  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);
1651
- 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);
1657
+ 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").option("--fail-on-drift", "Exit with code 1 if drift is detected (for CI)").action(driftCommand);
1652
1658
  program.command("check").description("Check external SSOT conformance (including custom models)").argument("[type]", "Type of check: external-ssot, openapi, ddl, iac, custom, all, test").option("-c, --config <path>", "Path to config file").option("--strict", "Treat warnings as errors").option("-v, --verbose", "Show detailed output").option("--coverage", "Check if all testable acceptance criteria are covered by TestRefs").action(checkCommand);
1653
1659
  program.command("new").description("Create a new element with auto-generated ID").argument("<type>", "Type: requirement, usecase, entity, component, screen, flow, error-case, term").option("-k, --kind <kind>", "Sub-kind (e.g., functional, non-functional for requirements)").option("-n, --name <name>", "Name of the element").option("-o, --output <path>", "Output directory path").option("-t, --template <path>", "Path to template file").action(newCommand);
1654
1660
  program.command("impact").description("Analyze impact of changes to an ID").argument("<id>", "ID to analyze (e.g., REQ-001, ENT-ORDER)").option("-c, --config <path>", "Path to config file").option("-d, --depth <depth>", "Analysis depth (reference tracking level)", "3").option("--direction <direction>", "Analysis direction: upstream, downstream, both", "both").option("-f, --format <format>", "Output format: text, json, mermaid", "text").action(impactCommand);