speckeeper 0.10.0 → 0.11.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.
@@ -9,6 +9,11 @@
9
9
  | init | Initialize a new speckeeper project with starter templates |
10
10
  | new | Create a new element with auto-generated ID |
11
11
  | scaffold | Generate _models/ from a mermaid flowchart definition |
12
+ | audit-requirements | Semantic requirement quality audit via LLM (verifiability, ambiguity, granularity, terminology, design-mixing) |
13
+ | propose-trace-links | Propose candidate traceability links between specs with confidence scores and rationale |
14
+ | explain-impact | Translate impact analysis JSON (from stdin) into human-readable explanation for PM/executive audiences |
15
+ | propose-acceptance-criteria | Propose testable acceptance criteria in Given/When/Then format for specified specs |
16
+ | convert | Convert a TS spec data file to YAML format |
12
17
  | impact | Analyze the change impact scope of a specified ID |
13
18
 
14
19
  ---
@@ -293,6 +298,218 @@ speckeeper scaffold -s spec.md --dry-run
293
298
 
294
299
  ---
295
300
 
301
+ ## CMD-AUDIT-REQ: audit-requirements
302
+
303
+ Semantic requirement quality audit via LLM (verifiability, ambiguity, granularity, terminology, design-mixing)
304
+
305
+ ### Usage
306
+
307
+ ```bash
308
+ speckeeper audit-requirements [options]
309
+ ```
310
+
311
+ ### Parameters
312
+
313
+ | Name | Kind | Type | Required | Default | Description |
314
+ |------|------|------|----------|---------|-------------|
315
+ | -c, --config | option | path | | - | Path to config file |
316
+ | -a, --adapter | option | enum | | - | SDK adapter for LLM execution |
317
+ | --model | option | string | | - | LLM model override |
318
+ | -n, --dry-run | option | boolean | | false | Output constructed prompt without calling LLM |
319
+ | --fail-on | option | enum | | error | Minimum severity for non-zero exit |
320
+ | -o, --output | option | path | | - | Write result to file instead of stdout |
321
+ | --report-format | option | enum | | json | Output format for audit report |
322
+ | --show-prompt | option | boolean | | false | Display constructed LLM prompt on stderr |
323
+
324
+ ### Examples
325
+
326
+ ```bash
327
+ speckeeper audit-requirements
328
+ speckeeper audit-requirements --adapter openai --dry-run
329
+ speckeeper audit-requirements --report-format json --output audit.json
330
+ ```
331
+
332
+ ### Exit Codes
333
+
334
+ | Code | Description |
335
+ |------|-------------|
336
+ | 0 | No blocking findings |
337
+ | 1 | Unexpected error |
338
+ | 2 | Configuration or input error |
339
+ | 10 | Completed with blocking findings |
340
+ | 11 | Runtime dependency missing |
341
+ | 12 | LLM provider or adapter error |
342
+
343
+ ---
344
+
345
+ ## CMD-PROPOSE-TRACE: propose-trace-links
346
+
347
+ Propose candidate traceability links between specs with confidence scores and rationale
348
+
349
+ ### Usage
350
+
351
+ ```bash
352
+ speckeeper propose-trace-links [options]
353
+ ```
354
+
355
+ ### Parameters
356
+
357
+ | Name | Kind | Type | Required | Default | Description |
358
+ |------|------|------|----------|---------|-------------|
359
+ | -c, --config | option | path | | - | Path to config file |
360
+ | -a, --adapter | option | enum | | - | SDK adapter for LLM execution |
361
+ | --model | option | string | | - | LLM model override |
362
+ | -n, --dry-run | option | boolean | | false | Output constructed prompt without calling LLM |
363
+ | --fail-on | option | enum | | error | Minimum severity for non-zero exit |
364
+ | -o, --output | option | path | | - | Write result to file instead of stdout |
365
+ | --report-format | option | enum | | json | Output format for report |
366
+ | --show-prompt | option | boolean | | false | Display constructed LLM prompt on stderr |
367
+
368
+ ### Examples
369
+
370
+ ```bash
371
+ speckeeper propose-trace-links
372
+ speckeeper propose-trace-links --adapter claude --report-format json
373
+ speckeeper propose-trace-links --dry-run
374
+ ```
375
+
376
+ ### Exit Codes
377
+
378
+ | Code | Description |
379
+ |------|-------------|
380
+ | 0 | No blocking findings |
381
+ | 1 | Unexpected error |
382
+ | 2 | Configuration or input error |
383
+ | 10 | Completed with blocking findings |
384
+ | 11 | Runtime dependency missing |
385
+ | 12 | LLM provider or adapter error |
386
+
387
+ ---
388
+
389
+ ## CMD-EXPLAIN-IMPACT: explain-impact
390
+
391
+ Translate impact analysis JSON (from stdin) into human-readable explanation for PM/executive audiences
392
+
393
+ ### Usage
394
+
395
+ ```bash
396
+ speckeeper explain-impact [options]
397
+ ```
398
+
399
+ ### Parameters
400
+
401
+ | Name | Kind | Type | Required | Default | Description |
402
+ |------|------|------|----------|---------|-------------|
403
+ | -c, --config | option | path | | - | Path to config file |
404
+ | -a, --adapter | option | enum | | - | SDK adapter for LLM execution |
405
+ | --model | option | string | | - | LLM model override |
406
+ | -n, --dry-run | option | boolean | | false | Output constructed prompt without calling LLM |
407
+ | --fail-on | option | enum | | error | Minimum severity for non-zero exit |
408
+ | -o, --output | option | path | | - | Write result to file instead of stdout |
409
+ | --report-format | option | enum | | json | Output format for report |
410
+ | --show-prompt | option | boolean | | false | Display constructed LLM prompt on stderr |
411
+
412
+ ### Examples
413
+
414
+ ```bash
415
+ speckeeper impact FR-001 --format json | speckeeper explain-impact
416
+ speckeeper impact ENT-ORDER --format json | speckeeper explain-impact --adapter claude
417
+ ```
418
+
419
+ ### Exit Codes
420
+
421
+ | Code | Description |
422
+ |------|-------------|
423
+ | 0 | Explanation completed |
424
+ | 1 | Unexpected error |
425
+ | 2 | Configuration or input error |
426
+ | 3 | No input on stdin |
427
+ | 10 | Completed with blocking findings |
428
+ | 11 | Runtime dependency missing |
429
+ | 12 | LLM provider or adapter error |
430
+
431
+ ---
432
+
433
+ ## CMD-PROPOSE-AC: propose-acceptance-criteria
434
+
435
+ Propose testable acceptance criteria in Given/When/Then format for specified specs
436
+
437
+ ### Usage
438
+
439
+ ```bash
440
+ speckeeper propose-acceptance-criteria [options]
441
+ ```
442
+
443
+ ### Parameters
444
+
445
+ | Name | Kind | Type | Required | Default | Description |
446
+ |------|------|------|----------|---------|-------------|
447
+ | <specIds> | argument | string | | - | Spec IDs to propose criteria for (defaults to all) |
448
+ | -c, --config | option | path | | - | Path to config file |
449
+ | -a, --adapter | option | enum | | - | SDK adapter for LLM execution |
450
+ | --model | option | string | | - | LLM model override |
451
+ | -n, --dry-run | option | boolean | | false | Output constructed prompt without calling LLM |
452
+ | --fail-on | option | enum | | error | Minimum severity for non-zero exit |
453
+ | -o, --output | option | path | | - | Write result to file instead of stdout |
454
+ | --report-format | option | enum | | json | Output format for report |
455
+ | --show-prompt | option | boolean | | false | Display constructed LLM prompt on stderr |
456
+
457
+ ### Examples
458
+
459
+ ```bash
460
+ speckeeper propose-acceptance-criteria
461
+ speckeeper propose-acceptance-criteria FR-001 FR-002
462
+ speckeeper propose-acceptance-criteria --adapter gemini --dry-run
463
+ ```
464
+
465
+ ### Exit Codes
466
+
467
+ | Code | Description |
468
+ |------|-------------|
469
+ | 0 | No blocking findings |
470
+ | 1 | Unexpected error |
471
+ | 2 | Configuration or input error |
472
+ | 10 | Completed with blocking findings |
473
+ | 11 | Runtime dependency missing |
474
+ | 12 | LLM provider or adapter error |
475
+
476
+ ---
477
+
478
+ ## CMD-CONVERT: convert
479
+
480
+ Convert a TS spec data file to YAML format
481
+
482
+ ### Usage
483
+
484
+ ```bash
485
+ speckeeper convert [options]
486
+ ```
487
+
488
+ ### Parameters
489
+
490
+ | Name | Kind | Type | Required | Default | Description |
491
+ |------|------|------|----------|---------|-------------|
492
+ | <file> | argument | path | ✓ | - | Path to TS spec data file |
493
+ | -o, --output | option | path | | - | Output file path (default: same name with .yaml extension) |
494
+ | -n, --dry-run | option | boolean | | false | Preview conversion without writing |
495
+
496
+ ### Examples
497
+
498
+ ```bash
499
+ speckeeper convert design/glossary.ts
500
+ speckeeper convert design/requirements.ts --output reqs.yaml
501
+ speckeeper convert design/glossary.ts --dry-run
502
+ ```
503
+
504
+ ### Exit Codes
505
+
506
+ | Code | Description |
507
+ |------|-------------|
508
+ | 0 | Conversion successful |
509
+ | 1 | Conversion error |
510
+
511
+ ---
512
+
296
513
  ## CMD-IMPACT: impact
297
514
 
298
515
  Analyze the change impact scope of a specified ID
@@ -51,6 +51,11 @@
51
51
  | FR-1017 | Source Path Fallback | must | check |
52
52
  | FR-1018 | Minimal New Dependencies | must | check |
53
53
  | FR-1019 | Checker Documentation Accuracy | must | check |
54
+ | FR-1100 | LLM-Powered Requirement Audit | should | llm |
55
+ | FR-1101 | LLM-Powered Trace Link Proposal | should | llm |
56
+ | FR-1102 | LLM-Powered Impact Explanation | should | llm |
57
+ | FR-1103 | LLM-Powered Acceptance Criteria Proposal | should | llm |
58
+ | FR-1104 | TypeScript to YAML Conversion | should | conversion |
54
59
 
55
60
  ---
56
61
 
@@ -395,8 +400,8 @@ Since consistency checks are implemented per model, filter by model name
395
400
  ### Acceptance Criteria
396
401
 
397
402
  - **FR-602-01**: speckeeper check runs external SSOT consistency check for all models [test]
398
- - **FR-602-02**: speckeeper check --model <model-name> checks only specific model [test]
399
- - **FR-602-03**: Model name is the model ID defined in design/_models/ [review]
403
+ - **FR-602-02**: speckeeper check [type] filters checks by type (openapi, ddl, iac, external-ssot, test, contract) [test]
404
+ - **FR-602-03**: Type argument corresponds to check categories, not model IDs [review]
400
405
  - **FR-602-04**: Only models with externalChecker are targeted [test]
401
406
 
402
407
  ---
@@ -807,4 +812,97 @@ README and scaffold-mermaid-spec.md accurately describe all three built-in check
807
812
  ### Acceptance Criteria
808
813
 
809
814
  - **FR-1019-01**: README checker table describes validation levels [review]
810
- - **FR-1019-02**: scaffold-mermaid-spec.md Section 7 describes validation levels [review]
815
+ - **FR-1019-02**: scaffold-mermaid-spec.md Section 7 describes validation levels [review]
816
+
817
+ ---
818
+
819
+ ## FR-1100: LLM-Powered Requirement Audit
820
+
821
+ **Type**: functional | **Priority**: should | **Category**: llm
822
+
823
+ Provide LLM-based semantic quality audit for requirement definitions, detecting verifiability issues, ambiguity, granularity problems, terminology inconsistency, and design-mixing
824
+
825
+ ### Rationale
826
+
827
+ Static lint rules cannot detect semantic issues such as ambiguous wording or design details mixed into requirements; LLM review complements structural checks
828
+
829
+ ### Acceptance Criteria
830
+
831
+ - **FR-1100-01**: audit-requirements command constructs a prompt from all registered specs and sends it to configured LLM adapter [test]
832
+ - **FR-1100-02**: Audit report includes findings with severity (error/warning/info) and affected spec IDs [test]
833
+ - **FR-1100-03**: --dry-run outputs the constructed prompt without calling LLM [test]
834
+ - **FR-1100-04**: --fail-on controls minimum severity that causes non-zero exit [test]
835
+ - **FR-1100-05**: Report format is selectable via --report-format (json, text, yaml) [test]
836
+
837
+ ---
838
+
839
+ ## FR-1101: LLM-Powered Trace Link Proposal
840
+
841
+ **Type**: functional | **Priority**: should | **Category**: llm
842
+
843
+ Propose candidate traceability links between specs with confidence scores and rationale using LLM analysis
844
+
845
+ ### Rationale
846
+
847
+ Manual traceability maintenance is error-prone; LLM can identify semantically related specs that humans may overlook
848
+
849
+ ### Acceptance Criteria
850
+
851
+ - **FR-1101-01**: propose-trace-links command analyzes all specs and proposes missing trace links [test]
852
+ - **FR-1101-02**: Each proposed link includes source ID, target ID, relation type, confidence score, and rationale [test]
853
+ - **FR-1101-03**: --dry-run outputs the constructed prompt without calling LLM [test]
854
+
855
+ ---
856
+
857
+ ## FR-1102: LLM-Powered Impact Explanation
858
+
859
+ **Type**: functional | **Priority**: should | **Category**: llm
860
+
861
+ Translate impact analysis JSON output into human-readable explanation for PM/executive audiences using LLM
862
+
863
+ ### Rationale
864
+
865
+ Raw impact analysis JSON is not consumable by non-technical stakeholders; LLM can generate natural language summaries
866
+
867
+ ### Acceptance Criteria
868
+
869
+ - **FR-1102-01**: explain-impact command reads impact analysis JSON from stdin [test]
870
+ - **FR-1102-02**: Output is a human-readable explanation suitable for PM/executive audiences [review]
871
+ - **FR-1102-03**: --dry-run outputs the constructed prompt without calling LLM [test]
872
+
873
+ ---
874
+
875
+ ## FR-1103: LLM-Powered Acceptance Criteria Proposal
876
+
877
+ **Type**: functional | **Priority**: should | **Category**: llm
878
+
879
+ Propose testable acceptance criteria in Given/When/Then format for specified specs using LLM
880
+
881
+ ### Rationale
882
+
883
+ Writing testable acceptance criteria is time-consuming; LLM can propose initial criteria that humans refine
884
+
885
+ ### Acceptance Criteria
886
+
887
+ - **FR-1103-01**: propose-acceptance-criteria command generates criteria for specified spec IDs (or all) [test]
888
+ - **FR-1103-02**: Proposed criteria follow Given/When/Then format [review]
889
+ - **FR-1103-03**: --dry-run outputs the constructed prompt without calling LLM [test]
890
+
891
+ ---
892
+
893
+ ## FR-1104: TypeScript to YAML Conversion
894
+
895
+ **Type**: functional | **Priority**: should | **Category**: conversion
896
+
897
+ Convert TypeScript spec data files to equivalent YAML format, enabling non-TypeScript workflows and interoperability
898
+
899
+ ### Rationale
900
+
901
+ YAML input lowers participation barriers for non-developers (NFR-005) and enables interoperability with external tools
902
+
903
+ ### Acceptance Criteria
904
+
905
+ - **FR-1104-01**: convert command reads a TS file exporting a SpecModule via defineSpecs() and writes equivalent YAML [test]
906
+ - **FR-1104-02**: Output defaults to same filename with .yaml extension [test]
907
+ - **FR-1104-03**: --output allows specifying a custom output path [test]
908
+ - **FR-1104-04**: --dry-run previews conversion without writing files [test]
@@ -214,15 +214,15 @@ To ensure all acceptance criteria are covered by test cases and maintain spec-te
214
214
 
215
215
  **Type**: non-functional | **Priority**: must | **Category**: testability
216
216
 
217
- CLI command definitions in design/cli-commands.ts match actual implementation in src/cli/index.ts
217
+ CLI command definitions in design/cli-commands.ts match cli-contract.yaml and generated code in src/generated/
218
218
 
219
219
  ### Rationale
220
220
 
221
- To ensure specification and implementation stay synchronized (e.g., no missing --config parameters)
221
+ To ensure specification, DSL contract, and generated implementation stay synchronized
222
222
 
223
223
  ### Acceptance Criteria
224
224
 
225
- - **NFR-014-01**: All command definitions in design/cli-commands.ts match implementation (parameters, subcommands, exit codes) [test]
225
+ - **NFR-014-01**: All command definitions in design/cli-commands.ts match cli-contract.yaml and generated code (parameters, subcommands, exit codes) [test]
226
226
 
227
227
  ---
228
228
 
@@ -12,6 +12,7 @@
12
12
  | TEST-023 | Impact command verification test | vitest | 1 |
13
13
  | TEST-024 | Drift command verification test | vitest | 1 |
14
14
  | TEST-025 | New command verification test | vitest | 1 |
15
+ | TEST-026 | Scaffold integration verification test (mermaid parsing, class-based generation) | vitest | 1 |
15
16
 
16
17
  ---
17
18
 
@@ -256,3 +257,28 @@
256
257
  | FR-104-01 | `FR-104-01.*available model types header` | Outputs model types header when type omitted |
257
258
 
258
259
  ---
260
+
261
+ ## TEST-026: Scaffold integration verification test (mermaid parsing, class-based generation)
262
+
263
+ ### Test Source
264
+
265
+ - **Path**: `test/scaffold/integration.test.ts`
266
+ - **Framework**: vitest
267
+
268
+ ### Verified Requirements
269
+
270
+ - FR-106
271
+
272
+ ### Implemented Command
273
+
274
+ - CMD-SCAFFOLD
275
+
276
+ ### Test Case Patterns
277
+
278
+ | Acceptance Criteria ID | Pattern | Description |
279
+ |------------------------|---------|-------------|
280
+ | FR-106-01 | `base template.*core factory|generated models.*base template` | Artifact class generates from base template |
281
+ | FR-106-03 | `SR.*FR.*NFR.*map to requirement.*de-duplicated` | Same-class node aggregation into single model file |
282
+ | FR-106-05 | `de-duplicated model files.*spec data` | Model file generation with naming conventions |
283
+
284
+ ---
@@ -109,7 +109,10 @@ export interface Exporter<T> {
109
109
  format: 'markdown' | 'json' | 'mermaid';
110
110
  single?: (spec: T) => string;
111
111
  index?: (specs: T[]) => string;
112
+ /** Subdirectory under docsDir (used with single + index/index.md) */
112
113
  outputDir?: string;
114
+ /** Direct output file path relative to docsDir (used with index-only exporters) */
115
+ outputFile?: string;
113
116
  filename?: (spec: T) => string;
114
117
  }
115
118
 
@@ -138,34 +141,31 @@ export interface CheckResult {
138
141
  }>;
139
142
  }
140
143
 
141
- /**
142
- * Coverage result
143
- */
144
- export interface CoverageResult {
145
- /** Total target count */
146
- total: number;
147
- /** Covered count */
148
- covered: number;
149
- /** Uncovered count */
150
- uncovered: number;
151
- /** Coverage rate (%) */
152
- coveragePercent: number;
153
- /** Details of covered items */
154
- coveredItems: { id: string; description?: string }[];
155
- /** Details of uncovered items */
156
- uncoveredItems: { id: string; description?: string; sourceId?: string }[];
144
+ // ============================================================================
145
+ // Deep Validation (replaces per-model externalChecker)
146
+ // ============================================================================
147
+
148
+ /** OpenAPI deep validation mapping */
149
+ export interface OpenAPIValidationMapping {
150
+ path: string;
151
+ method?: string;
152
+ parameters?: Array<{ name: string; in?: string; type?: string }>;
153
+ responseProperties?: Array<{ name: string; type?: string }>;
154
+ }
155
+
156
+ /** DDL deep validation mapping */
157
+ export interface DDLValidationMapping {
158
+ tableName: string;
159
+ columns?: Array<{ name: string; type?: string }>;
160
+ checkTypes?: boolean;
157
161
  }
158
162
 
159
163
  /**
160
- * Coverage checker definition
161
- *
162
- * Verify cross-model consistency (coverage).
163
- * Example: Whether TestRef covers acceptanceCriteria of Requirement
164
+ * Deep validation rule for a specific source type.
165
+ * The mapper extracts expected structure from a spec for detailed comparison
166
+ * against the matched source object.
164
167
  */
165
- export interface CoverageChecker<T> {
166
- /** Target model ID for coverage (e.g. 'requirement') */
167
- targetModel: string;
168
- /** Description of coverage check */
168
+ export interface DeepValidationRule<T, TMapping = unknown> {
169
169
  ```
170
170
  <!--@embedoc:end-->
171
171
 
@@ -175,54 +175,54 @@ export interface CoverageChecker<T> {
175
175
  **src/core/model.ts (Model Class Properties)**
176
176
 
177
177
  ```typescript
178
- /** Execute coverage check */
179
- check: (
180
- specs: T[],
181
- registry: Record<string, Map<string, unknown>>
182
- ) => CoverageResult;
183
178
  }
184
179
 
185
- // ============================================================================
186
- // Renderer (for embeds)
187
- // ============================================================================
188
-
189
180
  /**
190
- * Render context
191
- * Simplified version compatible with embedoc's EmbedContext
181
+ * Deep validation configuration keyed by source type.
182
+ * Models define this to enable Level 2/3 checks beyond existence.
192
183
  */
193
- export interface RenderContext {
194
- /** Parameters (filter conditions other than format, etc.) */
195
- params: Record<string, string | undefined>;
196
- /** Markdown helpers */
197
- markdown: {
198
- /** Generate table */
199
- table: (headers: string[], rows: (string | unknown)[][]) => string;
200
- };
184
+ export interface DeepValidationConfig<T> {
185
+ openapi?: DeepValidationRule<T, OpenAPIValidationMapping>;
186
+ ddl?: DeepValidationRule<T, DDLValidationMapping>;
187
+ [sourceType: string]: DeepValidationRule<T, unknown> | undefined;
201
188
  }
202
189
 
203
190
  /**
204
- * Renderer definition
205
- *
206
- * Model-specific rendering called from embeds
191
+ * Lookup key configuration keyed by source type.
192
+ * When a model's spec ID differs from the external identifier
193
+ * (e.g. entity ID "user" vs DDL table name "users"),
194
+ * define a mapper per source type to derive the external key.
195
+ * If not defined for a source type, spec.id is used as-is.
207
196
  */
208
- export interface Renderer<T> {
209
- /** Format ID ('table', 'list', 'detail', 'spec-chapter', etc.) */
210
- format: string;
211
- /** Rendering process */
212
- render: (specs: T[], ctx: RenderContext) => string;
197
+ export interface LookupKeyConfig<T> {
198
+ [sourceType: string]: ((spec: T) => string) | undefined;
213
199
  }
214
200
 
215
- // ============================================================================
216
- // Model Base Class
217
- // ============================================================================
201
+ /**
202
+ * Coverage result
203
+ */
204
+ export interface CoverageResult {
205
+ /** Total target count */
206
+ total: number;
207
+ /** Covered count */
208
+ covered: number;
209
+ /** Uncovered count */
210
+ uncovered: number;
211
+ /** Coverage rate (%) */
212
+ coveragePercent: number;
213
+ /** Details of covered items */
214
+ coveredItems: { id: string; description?: string }[];
215
+ /** Details of uncovered items */
216
+ uncoveredItems: { id: string; description?: string; sourceId?: string }[];
217
+ }
218
218
 
219
219
  /**
220
- * Model base class
220
+ * Coverage checker definition
221
221
  *
222
- * @template TSchema - Zod schema type
222
+ * Verify cross-model consistency (coverage).
223
+ * Example: Whether TestRef covers acceptanceCriteria of Requirement
223
224
  */
224
- export abstract class Model<TSchema extends ZodType> {
225
- /** Singleton instance storage (per subclass) */
225
+ export interface CoverageChecker<T> {
226
226
  ```
227
227
  <!--@embedoc:end-->
228
228
 
@@ -493,61 +493,65 @@ class TestRefModel extends Model<typeof TestRefSchema> {
493
493
  protected exporters: Exporter<TestRef>[] = [
494
494
  {
495
495
  format: 'markdown',
496
- single: (spec) => {
496
+ index: (specs) => {
497
497
  const lines: string[] = [];
498
- lines.push(`# ${spec.id}: ${spec.description}`);
499
- lines.push('');
500
- lines.push('## Test Source');
498
+ lines.push('# Test Reference List');
501
499
  lines.push('');
502
- lines.push(`- **Path**: \`${spec.source.path}\``);
503
- lines.push(`- **Framework**: ${spec.source.framework}`);
504
- if (spec.source.resultPath) {
505
- lines.push(`- **Result JSON**: \`${spec.source.resultPath}\``);
500
+ lines.push('| ID | Description | Framework | Requirements Count |');
501
+ lines.push('|----|-------------|-----------|-------------------|');
502
+ for (const spec of specs) {
503
+ lines.push(
504
+ `| ${spec.id} | ${spec.description} | ${spec.source.framework} | ${spec.verifiesRequirements.length} |`,
505
+ );
506
506
  }
507
507
  lines.push('');
508
-
509
- lines.push('## Verified Requirements');
510
- lines.push('');
511
- for (const reqId of spec.verifiesRequirements) {
512
- lines.push(`- ${reqId}`);
513
- }
508
+ lines.push('---');
514
509
  lines.push('');
515
510
 
516
- if (spec.implementsCommand) {
517
- lines.push('## Implemented Command');
511
+ for (const spec of specs) {
512
+ lines.push(`## ${spec.id}: ${spec.description}`);
518
513
  lines.push('');
519
- lines.push(`- ${spec.implementsCommand}`);
514
+ lines.push('### Test Source');
515
+ lines.push('');
516
+ lines.push(`- **Path**: \`${spec.source.path}\``);
517
+ lines.push(`- **Framework**: ${spec.source.framework}`);
518
+ if (spec.source.resultPath) {
519
+ lines.push(`- **Result JSON**: \`${spec.source.resultPath}\``);
520
+ }
520
521
  lines.push('');
521
- }
522
522
 
523
- if (spec.testCasePatterns && spec.testCasePatterns.length > 0) {
524
- lines.push('## Test Case Patterns');
523
+ lines.push('### Verified Requirements');
525
524
  lines.push('');
526
- lines.push('| Acceptance Criteria ID | Pattern | Description |');
527
- lines.push('|------------------------|---------|-------------|');
528
- for (const p of spec.testCasePatterns) {
529
- lines.push(`| ${p.acceptanceCriteriaId} | \`${p.pattern}\` | ${p.description || '-'} |`);
525
+ for (const reqId of spec.verifiesRequirements) {
526
+ lines.push(`- ${reqId}`);
530
527
  }
531
528
  lines.push('');
532
- }
533
529
 
534
- return lines.join('\n');
535
- },
536
- index: (specs) => {
537
- const lines: string[] = [];
538
- lines.push('# Test Reference List');
539
- lines.push('');
540
- lines.push('| ID | Description | Framework | Requirements Count |');
541
- lines.push('|----|-------------|-----------|-------------------|');
542
- for (const spec of specs) {
543
- lines.push(
544
- `| [${spec.id}](./${spec.id}.md) | ${spec.description} | ${spec.source.framework} | ${spec.verifiesRequirements.length} |`,
545
- );
530
+ if (spec.implementsCommand) {
531
+ lines.push('### Implemented Command');
532
+ lines.push('');
533
+ lines.push(`- ${spec.implementsCommand}`);
534
+ lines.push('');
535
+ }
536
+
537
+ if (spec.testCasePatterns && spec.testCasePatterns.length > 0) {
538
+ lines.push('### Test Case Patterns');
539
+ lines.push('');
540
+ lines.push('| Acceptance Criteria ID | Pattern | Description |');
541
+ lines.push('|------------------------|---------|-------------|');
542
+ for (const p of spec.testCasePatterns) {
543
+ lines.push(`| ${p.acceptanceCriteriaId} | \`${p.pattern}\` | ${p.description || '-'} |`);
544
+ }
545
+ lines.push('');
546
+ }
547
+
548
+ lines.push('---');
549
+ lines.push('');
546
550
  }
547
- return lines.join('\n');
551
+
552
+ return lines.join('\n').replace(/\n---\n\n$/s, '\n');
548
553
  },
549
- outputDir: 'test-refs',
550
- filename: (spec) => spec.id,
554
+ outputFile: 'design/test-refs.md',
551
555
  },
552
556
  ];
553
557