@anselmdk/feature-spec-md 0.1.1-rc.6 → 0.2.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 +89 -61
- package/SPEC_FORMAT.md +44 -17
- package/dist/cli.js +5 -1
- package/dist/cli.js.map +1 -1
- package/dist/featureSpecs.d.ts +34 -0
- package/dist/featureSpecs.d.ts.map +1 -0
- package/dist/featureSpecs.js +240 -0
- package/dist/featureSpecs.js.map +1 -0
- package/dist/filePatterns.d.ts.map +1 -1
- package/dist/filePatterns.js +4 -0
- package/dist/filePatterns.js.map +1 -1
- package/dist/html.d.ts +3 -0
- package/dist/html.d.ts.map +1 -1
- package/dist/html.js +3 -0
- package/dist/html.js.map +1 -1
- package/dist/index.d.ts +3 -34
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -338
- package/dist/index.js.map +1 -1
- package/dist/playwright.d.ts.map +1 -1
- package/dist/playwright.js +13 -21
- package/dist/playwright.js.map +1 -1
- package/dist/reportTemplate.d.ts.map +1 -1
- package/dist/reportTemplate.js +4 -0
- package/dist/reportTemplate.js.map +1 -1
- package/dist/screenshots.d.ts.map +1 -1
- package/dist/screenshots.js +4 -0
- package/dist/screenshots.js.map +1 -1
- package/dist/specDocuments.d.ts.map +1 -1
- package/dist/specDocuments.js +16 -112
- package/dist/specDocuments.js.map +1 -1
- package/dist/specMarkdown.d.ts +26 -0
- package/dist/specMarkdown.d.ts.map +1 -0
- package/dist/specMarkdown.js +126 -0
- package/dist/specMarkdown.js.map +1 -0
- package/dist/testImplementationReport.d.ts +4 -0
- package/dist/testImplementationReport.d.ts.map +1 -1
- package/dist/testImplementationReport.js.map +1 -1
- package/dist/types.d.ts +4 -0
- package/dist/types.d.ts.map +1 -1
- package/docs/releasing.md +107 -0
- package/docs/spec-driven-flow.md +175 -0
- package/package.json +8 -2
package/README.md
CHANGED
|
@@ -1,83 +1,123 @@
|
|
|
1
1
|
# feature-spec-md
|
|
2
2
|
|
|
3
|
-
Markdown
|
|
3
|
+
Markdown specs for AI-assisted, testable spec driven development.
|
|
4
4
|
|
|
5
|
-
The
|
|
5
|
+
The concept is deliberately small:
|
|
6
6
|
|
|
7
7
|
```txt
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
model + features + stack + design
|
|
9
|
+
-> AI-written executable tests that reference stable spec IDs
|
|
10
|
+
-> validation, coverage, screenshots, and reports
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
The
|
|
13
|
+
The specs are meant to be written with an AI before implementation. The tests are meant to be written with an AI from those specs. `feature-spec-md` checks that the Markdown stays structured and that executable tests still cover the model items, rules, and scenarios the specs define.
|
|
14
14
|
|
|
15
|
-
##
|
|
15
|
+
## What You Write
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
Use four ordinary Markdown document types:
|
|
18
18
|
|
|
19
19
|
```txt
|
|
20
|
-
*.model.md
|
|
21
|
-
*.feature.md
|
|
20
|
+
*.model.md shared domain vocabulary
|
|
21
|
+
*.feature.md user-facing behavior, rules, and scenarios
|
|
22
|
+
*.stack.md technical platform choices
|
|
23
|
+
*.design.md product, UI, and interaction direction
|
|
22
24
|
```
|
|
23
25
|
|
|
24
|
-
|
|
26
|
+
Each document has frontmatter, a short `## Purpose`, and stable IDs. Tests reference those IDs in titles, tags, annotations, comments, or metadata.
|
|
25
27
|
|
|
26
|
-
|
|
27
|
-
-
|
|
28
|
-
- optional `## Rules` for global invariants
|
|
28
|
+
```md
|
|
29
|
+
### ACCOUNT-ACCESS-S001: Registered person signs in
|
|
29
30
|
|
|
30
|
-
|
|
31
|
+
Given a registered person is on the sign-in page
|
|
32
|
+
When they request and open a valid sign-in link
|
|
33
|
+
Then they are signed in
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
test("ACCOUNT-ACCESS-S001 registered person signs in", async ({ page }) => {
|
|
38
|
+
// Covers ACCOUNT-ACCESS-R001 and ACCOUNT-M001.
|
|
39
|
+
});
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
See [SPEC_FORMAT.md](SPEC_FORMAT.md) for the exact document format.
|
|
43
|
+
|
|
44
|
+
## Install
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
npm install -D @anselmdk/feature-spec-md
|
|
48
|
+
```
|
|
31
49
|
|
|
32
|
-
|
|
33
|
-
- optional `model` or `models` references
|
|
34
|
-
- `## Rules` for durable product truths
|
|
35
|
-
- `## Scenarios` for Given / When / Then examples
|
|
50
|
+
Create starter specs:
|
|
36
51
|
|
|
37
|
-
|
|
52
|
+
```bash
|
|
53
|
+
npx feature-spec-md init --kind model --dir specs
|
|
54
|
+
npx feature-spec-md init --kind feature --dir specs
|
|
55
|
+
npx feature-spec-md init --kind stack --dir specs
|
|
56
|
+
npx feature-spec-md init --kind design --dir specs
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Workflow
|
|
60
|
+
|
|
61
|
+
1. Ask an AI to draft or update `*.model.md`, `*.feature.md`, `*.stack.md`, and `*.design.md` files.
|
|
62
|
+
2. Run `npx feature-spec-md check` until the spec set is valid.
|
|
63
|
+
3. Ask an AI to write executable tests from the specs, preserving the relevant `-M001`, `-R001`, and `-S001` IDs in the test source.
|
|
64
|
+
4. Run `npx feature-spec-md coverage` to see which scenarios, rules, and model items have tests.
|
|
65
|
+
5. Run `npx feature-spec-md report` to generate an HTML implementation report for review or CI artifacts.
|
|
66
|
+
|
|
67
|
+
The longer flow, including AI prompts and CI setup, is in [docs/spec-driven-flow.md](docs/spec-driven-flow.md).
|
|
38
68
|
|
|
39
69
|
## CLI
|
|
40
70
|
|
|
41
71
|
```bash
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
npm run dev -- report --specs "examples/**/*.feature.md" --tests "tests/**/*.test.ts"
|
|
46
|
-
npm run dev -- report --specs "examples/**/*.feature.md" --tests "tests/**/*.test.ts" --screenshots "test-results/spec-report/screenshots-*.json"
|
|
72
|
+
npx feature-spec-md check
|
|
73
|
+
npx feature-spec-md coverage --fail-on-missing
|
|
74
|
+
npx feature-spec-md report --out test-results/feature-spec-report/index.html
|
|
47
75
|
```
|
|
48
76
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
77
|
+
By default the CLI scans:
|
|
78
|
+
|
|
79
|
+
```txt
|
|
80
|
+
specs/**/*.model.md
|
|
81
|
+
specs/**/*.feature.md
|
|
82
|
+
specs/**/*.stack.md
|
|
83
|
+
specs/**/*.design.md
|
|
84
|
+
tests/**/*.spec.ts
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Use explicit patterns when your project uses different paths:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
npx feature-spec-md check \
|
|
91
|
+
--specs "product/**/*.model.md,product/**/*.feature.md,product/**/*.stack.md,product/**/*.design.md" \
|
|
92
|
+
--tests "e2e/**/*.spec.ts"
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
The `check` command validates spec structure, references between documents, and test coverage. Scenario coverage is required by default when tests are scanned. Use `--require-scenario-coverage=false` while drafting.
|
|
96
|
+
|
|
97
|
+
The `coverage` command prints a terminal implementation report. Use `--fail-on-missing` when missing scenario tests should fail CI.
|
|
98
|
+
|
|
99
|
+
The `report` command writes an HTML report. It can include screenshot evidence from Playwright or another test runner by passing one or more screenshot manifest files:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
npx feature-spec-md report \
|
|
103
|
+
--screenshots "test-results/spec-report/screenshots-*.json"
|
|
68
104
|
```
|
|
69
105
|
|
|
70
106
|
## Library API
|
|
71
107
|
|
|
108
|
+
Most integrations can use the top-level document API:
|
|
109
|
+
|
|
72
110
|
```ts
|
|
73
111
|
import {
|
|
74
|
-
|
|
75
|
-
|
|
112
|
+
checkSpecDocuments,
|
|
113
|
+
parseSpecDocument,
|
|
76
114
|
renderHtmlReport,
|
|
77
|
-
|
|
78
|
-
} from "feature-spec-md";
|
|
115
|
+
validateSpecDocument,
|
|
116
|
+
} from "@anselmdk/feature-spec-md";
|
|
79
117
|
```
|
|
80
118
|
|
|
119
|
+
Feature-only helpers such as `parseFeatureSpec` and `checkFeatureSpecs` remain available for compatibility.
|
|
120
|
+
|
|
81
121
|
## Development
|
|
82
122
|
|
|
83
123
|
```bash
|
|
@@ -89,16 +129,4 @@ npm run build
|
|
|
89
129
|
|
|
90
130
|
## Releases
|
|
91
131
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
Release candidates are published manually from GitHub Actions:
|
|
95
|
-
|
|
96
|
-
1. Open the `Publish RC to npm` workflow.
|
|
97
|
-
2. Run the workflow from `main`.
|
|
98
|
-
3. The workflow verifies the package, creates a prerelease version using the current package version and GitHub run number, and publishes it with the npm `rc` dist-tag.
|
|
99
|
-
|
|
100
|
-
Install the latest release candidate with:
|
|
101
|
-
|
|
102
|
-
```bash
|
|
103
|
-
npm install @anselmdk/feature-spec-md@rc
|
|
104
|
-
```
|
|
132
|
+
Release candidates and stable releases are documented in [docs/releasing.md](docs/releasing.md).
|
package/SPEC_FORMAT.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
# Feature Spec Markdown
|
|
1
|
+
# Feature Spec Markdown Format
|
|
2
2
|
|
|
3
|
-
Feature Spec Markdown is a lightweight
|
|
3
|
+
Feature Spec Markdown is a lightweight format for testable specs.
|
|
4
4
|
|
|
5
|
-
It uses ordinary Markdown
|
|
5
|
+
It uses ordinary Markdown plus stable IDs so an AI can write clear product specs, another AI pass can write executable tests from those specs, and tooling can verify that the tests still cover the documented behavior.
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## Document Set
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
A spec set can contain four document types:
|
|
10
10
|
|
|
11
11
|
```txt
|
|
12
12
|
*.model.md
|
|
@@ -15,10 +15,19 @@ The format has four document types:
|
|
|
15
15
|
*.design.md
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
- `*.model.md` defines shared domain vocabulary.
|
|
18
|
+
- `*.model.md` defines shared domain vocabulary and global domain rules.
|
|
19
19
|
- `*.feature.md` defines user-facing behavior with rules and scenarios.
|
|
20
|
-
- `*.stack.md` defines technical platform choices.
|
|
21
|
-
- `*.design.md` defines product, UI, and interaction
|
|
20
|
+
- `*.stack.md` defines technical platform choices and their rationale.
|
|
21
|
+
- `*.design.md` defines product, UI, layout, visual, and interaction direction.
|
|
22
|
+
|
|
23
|
+
The short version:
|
|
24
|
+
|
|
25
|
+
```txt
|
|
26
|
+
model tells tests what things mean
|
|
27
|
+
features tell tests what behavior must work
|
|
28
|
+
stack tells implementation what tools and constraints to use
|
|
29
|
+
design tells implementation what experience to create
|
|
30
|
+
```
|
|
22
31
|
|
|
23
32
|
## Frontmatter
|
|
24
33
|
|
|
@@ -45,13 +54,15 @@ Optional fields:
|
|
|
45
54
|
- `model`: a single referenced model id, for feature and design files
|
|
46
55
|
- `models`: comma-separated referenced model ids, for feature and design files
|
|
47
56
|
|
|
57
|
+
IDs use uppercase words separated by hyphens. IDs are the contract between specs, tests, reports, and implementation work.
|
|
58
|
+
|
|
48
59
|
## Purpose
|
|
49
60
|
|
|
50
61
|
Every model, feature, stack, and design file MUST include a short `## Purpose` section.
|
|
51
62
|
|
|
52
63
|
Purpose explains the document boundary and intent. It SHOULD be one or two short paragraphs and SHOULD NOT contain rules, scenarios, implementation details, or roadmap notes.
|
|
53
64
|
|
|
54
|
-
## Model
|
|
65
|
+
## Model Files
|
|
55
66
|
|
|
56
67
|
Model files use the `.model.md` suffix.
|
|
57
68
|
|
|
@@ -79,9 +90,13 @@ Model items use stable `-M001` IDs:
|
|
|
79
90
|
A card represents one work item on the board.
|
|
80
91
|
```
|
|
81
92
|
|
|
82
|
-
Model rules use stable `-R001` IDs and
|
|
93
|
+
Model rules use stable `-R001` IDs and describe global invariants for the domain vocabulary:
|
|
94
|
+
|
|
95
|
+
```md
|
|
96
|
+
- KANBAN-R001: A card MUST have one current workflow state.
|
|
97
|
+
```
|
|
83
98
|
|
|
84
|
-
## Feature
|
|
99
|
+
## Feature Files
|
|
85
100
|
|
|
86
101
|
Feature files use the `.feature.md` suffix.
|
|
87
102
|
|
|
@@ -121,7 +136,9 @@ Allowed step keywords:
|
|
|
121
136
|
- `And`
|
|
122
137
|
- `But`
|
|
123
138
|
|
|
124
|
-
|
|
139
|
+
Rules state durable product truths. Scenarios show concrete examples that executable tests can implement.
|
|
140
|
+
|
|
141
|
+
## Stack Files
|
|
125
142
|
|
|
126
143
|
Stack files use the `.stack.md` suffix.
|
|
127
144
|
|
|
@@ -171,7 +188,7 @@ Define the initial technical stack for implementing the Kanban board.
|
|
|
171
188
|
| Testing | Playwright |
|
|
172
189
|
```
|
|
173
190
|
|
|
174
|
-
## Design
|
|
191
|
+
## Design Files
|
|
175
192
|
|
|
176
193
|
Design files use the `.design.md` suffix.
|
|
177
194
|
|
|
@@ -220,17 +237,19 @@ Define the visual and interaction design direction for the Kanban board.
|
|
|
220
237
|
The board should feel lightweight, immediate, and calm.
|
|
221
238
|
```
|
|
222
239
|
|
|
223
|
-
## Splitting
|
|
240
|
+
## Splitting Guidance
|
|
224
241
|
|
|
225
242
|
Split model files by coherent domain vocabulary, ownership, or lifecycle.
|
|
226
243
|
|
|
227
|
-
|
|
244
|
+
Split feature files by user capability.
|
|
228
245
|
|
|
229
|
-
Use stack files for broad technical choices such as framework, language, testing, persistence, and
|
|
246
|
+
Use stack files for broad technical choices such as framework, language, testing, persistence, deployment, and runtime constraints.
|
|
230
247
|
|
|
231
248
|
Use design files for product/UI direction such as layout, interaction, visual style, and design principles.
|
|
232
249
|
|
|
233
|
-
|
|
250
|
+
Keep each document small enough for an AI to read, revise, and use as test-writing context without extra explanation.
|
|
251
|
+
|
|
252
|
+
## Test Coverage Convention
|
|
234
253
|
|
|
235
254
|
Spec files do not contain test mappings.
|
|
236
255
|
|
|
@@ -243,3 +262,11 @@ Generated tooling can answer:
|
|
|
243
262
|
- Which rules have executable coverage?
|
|
244
263
|
- Which tests reference deleted or unknown spec IDs?
|
|
245
264
|
- Which visible flows have screenshots, traces, or other evidence?
|
|
265
|
+
|
|
266
|
+
Example:
|
|
267
|
+
|
|
268
|
+
```ts
|
|
269
|
+
test("KANBAN-CARD-AUTHORING-S001 user creates a card", async ({ page }) => {
|
|
270
|
+
// Covers KANBAN-M001 and KANBAN-CARD-AUTHORING-R001.
|
|
271
|
+
});
|
|
272
|
+
```
|
package/dist/cli.js
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Command-line interface for initializing, validating, reporting on, and
|
|
4
|
+
* measuring test coverage for Feature Spec Markdown documents.
|
|
5
|
+
*/
|
|
2
6
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
7
|
import path from "node:path";
|
|
4
8
|
import { collectSpecScreenshots, renderHtmlReport, writeTextFile, } from "./index.js";
|
|
@@ -149,7 +153,7 @@ function printHelp() {
|
|
|
149
153
|
Usage:
|
|
150
154
|
feature-spec-md init [--kind feature|model|stack|design] [--dir specs]
|
|
151
155
|
feature-spec-md check [--specs "specs/**/*.model.md,specs/**/*.feature.md,specs/**/*.stack.md,specs/**/*.design.md"] [--tests "tests/**/*.spec.ts"]
|
|
152
|
-
feature-spec-md coverage [--specs "specs/**/*.feature.md"] [--tests "tests/**/*.spec.ts"] [--fail-on-missing]
|
|
156
|
+
feature-spec-md coverage [--specs "specs/**/*.model.md,specs/**/*.feature.md,specs/**/*.stack.md,specs/**/*.design.md"] [--tests "tests/**/*.spec.ts"] [--fail-on-missing]
|
|
153
157
|
feature-spec-md report [--specs "specs/**/*.model.md,specs/**/*.feature.md,specs/**/*.stack.md,specs/**/*.design.md"] [--tests "tests/**/*.spec.ts"] [--screenshots "test-results/spec-report/screenshots.json"] [--out test-results/feature-spec-report/index.html]
|
|
154
158
|
|
|
155
159
|
Options:
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { CoverageSummary, FeatureSpec, TestReference, ValidationIssue } from "./types.js";
|
|
2
|
+
/** Parse one feature spec Markdown document into structured metadata, rules, and scenarios. */
|
|
3
|
+
export declare function parseFeatureSpec(source: string, options?: {
|
|
4
|
+
filePath?: string;
|
|
5
|
+
}): FeatureSpec;
|
|
6
|
+
/** Validate spec structure, ID prefixes, duplicate IDs, and required sections. */
|
|
7
|
+
export declare function validateFeatureSpec(spec: FeatureSpec): ValidationIssue[];
|
|
8
|
+
/** Find rule and scenario IDs referenced by one test source file. */
|
|
9
|
+
export declare function parseTestReferences(source: string, filePath?: string): TestReference[];
|
|
10
|
+
/** Expand test file globs and collect all rule and scenario references from them. */
|
|
11
|
+
export declare function collectTestReferences(patterns: string[]): Promise<TestReference[]>;
|
|
12
|
+
/** Build rule and scenario coverage from parsed specs and collected test references. */
|
|
13
|
+
export declare function buildCoverageSummary(specs: FeatureSpec[], references: TestReference[]): CoverageSummary;
|
|
14
|
+
/** Validate coverage expectations and orphaned references. */
|
|
15
|
+
export declare function validateCoverage(coverage: CoverageSummary, options?: {
|
|
16
|
+
requireRuleCoverage?: boolean;
|
|
17
|
+
requireScenarioCoverage?: boolean;
|
|
18
|
+
}): ValidationIssue[];
|
|
19
|
+
/** Load and parse all feature specs matching the provided globs. */
|
|
20
|
+
export declare function loadFeatureSpecs(patterns: string[]): Promise<FeatureSpec[]>;
|
|
21
|
+
/** Run spec validation and optional test coverage validation in one call. */
|
|
22
|
+
export declare function checkFeatureSpecs(options: {
|
|
23
|
+
specs: string[];
|
|
24
|
+
tests?: string[];
|
|
25
|
+
requireRuleCoverage?: boolean;
|
|
26
|
+
requireScenarioCoverage?: boolean;
|
|
27
|
+
}): Promise<{
|
|
28
|
+
specs: FeatureSpec[];
|
|
29
|
+
validationIssues: ValidationIssue[];
|
|
30
|
+
coverage: CoverageSummary | undefined;
|
|
31
|
+
coverageIssues: ValidationIssue[];
|
|
32
|
+
ok: boolean;
|
|
33
|
+
}>;
|
|
34
|
+
//# sourceMappingURL=featureSpecs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"featureSpecs.d.ts","sourceRoot":"","sources":["../src/featureSpecs.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAEV,eAAe,EAGf,WAAW,EAEX,aAAa,EACb,eAAe,EAChB,MAAM,YAAY,CAAC;AAEpB,+FAA+F;AAC/F,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAClC,WAAW,CAab;AAED,kFAAkF;AAClF,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,WAAW,GAAG,eAAe,EAAE,CA2GxE;AAED,qEAAqE;AACrE,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,MAAM,EACd,QAAQ,SAAa,GACpB,aAAa,EAAE,CAyBjB;AAED,qFAAqF;AACrF,wBAAsB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,4BAM7D;AAED,wFAAwF;AACxF,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,WAAW,EAAE,EACpB,UAAU,EAAE,aAAa,EAAE,GAC1B,eAAe,CAkCjB;AAED,8DAA8D;AAC9D,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,eAAe,EACzB,OAAO,GAAE;IACP,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,uBAAuB,CAAC,EAAE,OAAO,CAAC;CAC9B,qBAmDP;AAED,oEAAoE;AACpE,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,0BAQxD;AAED,6EAA6E;AAC7E,wBAAsB,iBAAiB,CAAC,OAAO,EAAE;IAC/C,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,uBAAuB,CAAC,EAAE,OAAO,CAAC;CACnC;;;;;;GAsBA"}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feature-spec parsing, validation, test-reference extraction, and coverage
|
|
3
|
+
* checks for `*.feature.md` documents.
|
|
4
|
+
*/
|
|
5
|
+
import { readFile } from "node:fs/promises";
|
|
6
|
+
import { expandFilePatterns } from "./filePatterns.js";
|
|
7
|
+
import { dedupeTestReferences, lineForOffset, parseMarkdownDocument, parseRuleItems, ruleIdPattern, scenarioIdPattern, } from "./specMarkdown.js";
|
|
8
|
+
/** Parse one feature spec Markdown document into structured metadata, rules, and scenarios. */
|
|
9
|
+
export function parseFeatureSpec(source, options = {}) {
|
|
10
|
+
const filePath = options.filePath ?? "<inline>";
|
|
11
|
+
const parsed = parseMarkdownDocument(source, "Feature spec");
|
|
12
|
+
return {
|
|
13
|
+
filePath,
|
|
14
|
+
frontmatter: parsed.frontmatter,
|
|
15
|
+
title: parsed.title,
|
|
16
|
+
purpose: parsed.purpose,
|
|
17
|
+
rules: parseRuleItems(parsed.lines, parsed.bodyStartLine),
|
|
18
|
+
scenarios: parseScenarios(parsed.lines, parsed.bodyStartLine),
|
|
19
|
+
source,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/** Validate spec structure, ID prefixes, duplicate IDs, and required sections. */
|
|
23
|
+
export function validateFeatureSpec(spec) {
|
|
24
|
+
const issues = [];
|
|
25
|
+
const seen = new Map();
|
|
26
|
+
if (!/^[A-Z][A-Z0-9]*(?:-[A-Z0-9]+)*$/.test(spec.frontmatter.id)) {
|
|
27
|
+
issues.push(issue(spec, "invalid-feature-id", "error", `Feature id "${spec.frontmatter.id}" must use uppercase words separated by hyphens.`));
|
|
28
|
+
}
|
|
29
|
+
if (spec.title !== spec.frontmatter.title) {
|
|
30
|
+
issues.push(issue(spec, "title-mismatch", "warning", "The H1 title should match frontmatter.title."));
|
|
31
|
+
}
|
|
32
|
+
if (!spec.purpose) {
|
|
33
|
+
issues.push(issue(spec, "missing-purpose", "error", "Feature spec must include a non-empty ## Purpose section."));
|
|
34
|
+
}
|
|
35
|
+
if (spec.scenarios.length === 0) {
|
|
36
|
+
issues.push(issue(spec, "missing-scenarios", "error", "Feature spec must include at least one scenario."));
|
|
37
|
+
}
|
|
38
|
+
for (const rule of spec.rules) {
|
|
39
|
+
if (!rule.id.startsWith(`${spec.frontmatter.id}-`)) {
|
|
40
|
+
issues.push(issue(spec, "wrong-rule-prefix", "error", `Rule id "${rule.id}" must start with ${spec.frontmatter.id}-.`, rule.line));
|
|
41
|
+
}
|
|
42
|
+
if (!rule.keyword) {
|
|
43
|
+
issues.push(issue(spec, "missing-rule-keyword", "warning", `Rule "${rule.id}" should use MUST, MUST NOT, SHOULD, SHOULD NOT, MAY, or OPTIONAL.`, rule.line));
|
|
44
|
+
}
|
|
45
|
+
registerId(spec, issues, seen, rule.id, rule.line);
|
|
46
|
+
}
|
|
47
|
+
for (const scenario of spec.scenarios) {
|
|
48
|
+
if (!scenario.id.startsWith(`${spec.frontmatter.id}-`)) {
|
|
49
|
+
issues.push(issue(spec, "wrong-scenario-prefix", "error", `Scenario id "${scenario.id}" must start with ${spec.frontmatter.id}-.`, scenario.line));
|
|
50
|
+
}
|
|
51
|
+
for (const keyword of ["Given", "When", "Then"]) {
|
|
52
|
+
if (!scenario.steps.some((step) => step.keyword === keyword)) {
|
|
53
|
+
issues.push(issue(spec, `missing-${keyword.toLowerCase()}`, "warning", `Scenario "${scenario.id}" should include a ${keyword} step.`, scenario.line));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
registerId(spec, issues, seen, scenario.id, scenario.line);
|
|
57
|
+
}
|
|
58
|
+
return issues;
|
|
59
|
+
}
|
|
60
|
+
/** Find rule and scenario IDs referenced by one test source file. */
|
|
61
|
+
export function parseTestReferences(source, filePath = "<inline>") {
|
|
62
|
+
const lines = source.split(/\r?\n/);
|
|
63
|
+
const refs = [];
|
|
64
|
+
for (const match of source.matchAll(scenarioIdPattern)) {
|
|
65
|
+
refs.push({
|
|
66
|
+
id: match[0],
|
|
67
|
+
filePath,
|
|
68
|
+
line: lineForOffset(lines, match.index ?? 0),
|
|
69
|
+
kind: "scenario",
|
|
70
|
+
source: sourceForMatch(source, match.index ?? 0),
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
for (const match of source.matchAll(ruleIdPattern)) {
|
|
74
|
+
refs.push({
|
|
75
|
+
id: match[0],
|
|
76
|
+
filePath,
|
|
77
|
+
line: lineForOffset(lines, match.index ?? 0),
|
|
78
|
+
kind: "rule",
|
|
79
|
+
source: sourceForMatch(source, match.index ?? 0),
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
return dedupeTestReferences(refs);
|
|
83
|
+
}
|
|
84
|
+
/** Expand test file globs and collect all rule and scenario references from them. */
|
|
85
|
+
export async function collectTestReferences(patterns) {
|
|
86
|
+
const refs = [];
|
|
87
|
+
for (const file of await expandFilePatterns(patterns)) {
|
|
88
|
+
refs.push(...parseTestReferences(await readFile(file, "utf8"), file));
|
|
89
|
+
}
|
|
90
|
+
return refs;
|
|
91
|
+
}
|
|
92
|
+
/** Build rule and scenario coverage from parsed specs and collected test references. */
|
|
93
|
+
export function buildCoverageSummary(specs, references) {
|
|
94
|
+
const scenarios = new Map(specs.flatMap((spec) => spec.scenarios.map((scenario) => [scenario.id, { spec, scenario }])));
|
|
95
|
+
const rules = new Map(specs.flatMap((spec) => spec.rules.map((rule) => [rule.id, { spec, rule }])));
|
|
96
|
+
const scenarioRefs = references.filter((ref) => ref.kind === "scenario");
|
|
97
|
+
const ruleRefs = references.filter((ref) => ref.kind === "rule");
|
|
98
|
+
return {
|
|
99
|
+
scenarioCoverage: Array.from(scenarios.entries()).map(([id, value]) => item(id, value.scenario.title, value.spec.filePath, value.scenario.line, scenarioRefs)),
|
|
100
|
+
ruleCoverage: Array.from(rules.entries()).map(([id, value]) => item(id, value.rule.text, value.spec.filePath, value.rule.line, ruleRefs)),
|
|
101
|
+
orphanScenarioReferences: scenarioRefs.filter((ref) => !scenarios.has(ref.id)),
|
|
102
|
+
orphanRuleReferences: ruleRefs.filter((ref) => !rules.has(ref.id)),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/** Validate coverage expectations and orphaned references. */
|
|
106
|
+
export function validateCoverage(coverage, options = {}) {
|
|
107
|
+
const issues = [];
|
|
108
|
+
if (options.requireScenarioCoverage ?? true) {
|
|
109
|
+
for (const item of coverage.scenarioCoverage.filter((item) => !item.covered)) {
|
|
110
|
+
issues.push({
|
|
111
|
+
code: "missing-scenario-coverage",
|
|
112
|
+
severity: "error",
|
|
113
|
+
message: `Scenario "${item.id}" has no matching test reference.`,
|
|
114
|
+
filePath: item.filePath,
|
|
115
|
+
line: item.line,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (options.requireRuleCoverage ?? false) {
|
|
120
|
+
for (const item of coverage.ruleCoverage.filter((item) => !item.covered)) {
|
|
121
|
+
issues.push({
|
|
122
|
+
code: "missing-rule-coverage",
|
|
123
|
+
severity: "error",
|
|
124
|
+
message: `Rule "${item.id}" has no matching test reference.`,
|
|
125
|
+
filePath: item.filePath,
|
|
126
|
+
line: item.line,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
for (const ref of coverage.orphanScenarioReferences) {
|
|
131
|
+
issues.push({
|
|
132
|
+
code: "orphan-scenario-reference",
|
|
133
|
+
severity: "error",
|
|
134
|
+
message: `Test references unknown scenario "${ref.id}".`,
|
|
135
|
+
filePath: ref.filePath,
|
|
136
|
+
line: ref.line,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
for (const ref of coverage.orphanRuleReferences) {
|
|
140
|
+
issues.push({
|
|
141
|
+
code: "orphan-rule-reference",
|
|
142
|
+
severity: "error",
|
|
143
|
+
message: `Test references unknown rule "${ref.id}".`,
|
|
144
|
+
filePath: ref.filePath,
|
|
145
|
+
line: ref.line,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
return issues;
|
|
149
|
+
}
|
|
150
|
+
/** Load and parse all feature specs matching the provided globs. */
|
|
151
|
+
export async function loadFeatureSpecs(patterns) {
|
|
152
|
+
const specs = [];
|
|
153
|
+
for (const file of await expandFilePatterns(patterns)) {
|
|
154
|
+
specs.push(parseFeatureSpec(await readFile(file, "utf8"), { filePath: file }));
|
|
155
|
+
}
|
|
156
|
+
return specs;
|
|
157
|
+
}
|
|
158
|
+
/** Run spec validation and optional test coverage validation in one call. */
|
|
159
|
+
export async function checkFeatureSpecs(options) {
|
|
160
|
+
const specs = await loadFeatureSpecs(options.specs);
|
|
161
|
+
const validationIssues = specs.flatMap(validateFeatureSpec);
|
|
162
|
+
const coverage = options.tests?.length
|
|
163
|
+
? buildCoverageSummary(specs, await collectTestReferences(options.tests))
|
|
164
|
+
: undefined;
|
|
165
|
+
const coverageIssues = coverage
|
|
166
|
+
? validateCoverage(coverage, {
|
|
167
|
+
requireRuleCoverage: options.requireRuleCoverage,
|
|
168
|
+
requireScenarioCoverage: options.requireScenarioCoverage,
|
|
169
|
+
})
|
|
170
|
+
: [];
|
|
171
|
+
return {
|
|
172
|
+
specs,
|
|
173
|
+
validationIssues,
|
|
174
|
+
coverage,
|
|
175
|
+
coverageIssues,
|
|
176
|
+
ok: ![...validationIssues, ...coverageIssues].some((i) => i.severity === "error"),
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
function parseScenarios(lines, bodyStartLine) {
|
|
180
|
+
const scenarios = [];
|
|
181
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
182
|
+
const match = lines[i].match(/^###\s+([A-Z][A-Z0-9]*(?:-[A-Z0-9]+)*-S\d{3}):\s+(.+)$/);
|
|
183
|
+
if (!match)
|
|
184
|
+
continue;
|
|
185
|
+
const steps = [];
|
|
186
|
+
for (let j = i + 1; j < lines.length; j += 1) {
|
|
187
|
+
if (/^##?#\s+/.test(lines[j]))
|
|
188
|
+
break;
|
|
189
|
+
const step = lines[j].trim().match(/^(Given|When|Then|And|But)\s+(.+)$/);
|
|
190
|
+
if (step)
|
|
191
|
+
steps.push({
|
|
192
|
+
keyword: step[1],
|
|
193
|
+
text: step[2].trim(),
|
|
194
|
+
line: bodyStartLine + j,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
scenarios.push({
|
|
198
|
+
id: match[1],
|
|
199
|
+
title: match[2].trim(),
|
|
200
|
+
line: bodyStartLine + i,
|
|
201
|
+
steps,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
return scenarios;
|
|
205
|
+
}
|
|
206
|
+
function issue(spec, code, severity, message, line) {
|
|
207
|
+
return { code, severity, message, filePath: spec.filePath, line };
|
|
208
|
+
}
|
|
209
|
+
function registerId(spec, issues, seen, id, line) {
|
|
210
|
+
const previous = seen.get(id);
|
|
211
|
+
if (previous)
|
|
212
|
+
issues.push(issue(spec, "duplicate-id", "error", `Duplicate id "${id}". First occurrence is on line ${previous}.`, line));
|
|
213
|
+
seen.set(id, line);
|
|
214
|
+
}
|
|
215
|
+
function item(id, title, filePath, line, refs) {
|
|
216
|
+
const references = refs.filter((ref) => ref.id === id);
|
|
217
|
+
return {
|
|
218
|
+
id,
|
|
219
|
+
title,
|
|
220
|
+
filePath,
|
|
221
|
+
line,
|
|
222
|
+
references,
|
|
223
|
+
covered: references.length > 0,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
function sourceForMatch(source, offset) {
|
|
227
|
+
const before = source.slice(Math.max(0, offset - 80), offset);
|
|
228
|
+
const context = source.slice(Math.max(0, offset - 80), offset + 80);
|
|
229
|
+
if (/covers\s*:\s*\[[^\]]*$/.test(before))
|
|
230
|
+
return "covers";
|
|
231
|
+
if (/tag\s*:\s*\[[^\]]*$/.test(before) ||
|
|
232
|
+
/@/.test(source.slice(Math.max(0, offset - 2), offset + 2)))
|
|
233
|
+
return "tag";
|
|
234
|
+
if (/annotation/.test(context))
|
|
235
|
+
return "annotation";
|
|
236
|
+
if (/test|scenario/.test(before.slice(-40)))
|
|
237
|
+
return "title";
|
|
238
|
+
return "free-text";
|
|
239
|
+
}
|
|
240
|
+
//# sourceMappingURL=featureSpecs.js.map
|