opsx-gherkin 1.0.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.
Files changed (32) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/CONTRIBUTING.md +53 -0
  3. package/LICENSE +21 -0
  4. package/README.md +99 -0
  5. package/SECURITY.md +14 -0
  6. package/dist/cli.mjs +50 -0
  7. package/dist/cli.mjs.map +1 -0
  8. package/dist/converter-CFtzcfoM.mjs +977 -0
  9. package/dist/converter-CFtzcfoM.mjs.map +1 -0
  10. package/dist/mdg-to-opsx.mjs +8 -0
  11. package/dist/mdg-to-opsx.mjs.map +1 -0
  12. package/dist/official-99qBUZpr.mjs +91 -0
  13. package/dist/official-99qBUZpr.mjs.map +1 -0
  14. package/dist/opsx-to-feature.mjs +8 -0
  15. package/dist/opsx-to-feature.mjs.map +1 -0
  16. package/dist/opsx-to-mdg.mjs +8 -0
  17. package/dist/opsx-to-mdg.mjs.map +1 -0
  18. package/dist/validate-spec.mjs +56 -0
  19. package/dist/validate-spec.mjs.map +1 -0
  20. package/package.json +87 -0
  21. package/skills/opsx-gherkin/SKILL.md +144 -0
  22. package/skills/opsx-gherkin/references/OPENSPEC_MDG_PROFILE.feature.md +372 -0
  23. package/skills/opsx-gherkin/references/examples/account.feature +85 -0
  24. package/skills/opsx-gherkin/references/examples/account.feature.md +96 -0
  25. package/skills/opsx-gherkin/references/examples/account.openspec.md +109 -0
  26. package/skills/opsx-gherkin/references/examples/authentication-delta.feature +41 -0
  27. package/skills/opsx-gherkin/references/examples/authentication-delta.feature.md +45 -0
  28. package/skills/opsx-gherkin/references/examples/authentication-delta.openspec.md +52 -0
  29. package/skills/opsx-gherkin/references/examples/minimal.feature +17 -0
  30. package/skills/opsx-gherkin/references/examples/minimal.feature.md +19 -0
  31. package/skills/opsx-gherkin/references/examples/minimal.openspec.md +24 -0
  32. package/skills/opsx-gherkin/references/profile-access.feature.md +34 -0
@@ -0,0 +1,144 @@
1
+ ---
2
+ name: opsx-gherkin
3
+ description: Write, review, or convert specification syntax that must remain compatible between OpenSpec, Markdown with Gherkin (.feature.md), and plain Gherkin (.feature) using opsx-gherkin. Use for format mappings, compatibility constraints, and conversion diagnostics; ordinary prose editing or choosing application behavior does not need this skill.
4
+ ---
5
+
6
+ # OpenSpec and Gherkin compatibility
7
+
8
+ Preserve the intended behavior while expressing it in the requested format.
9
+ Read the [canonical compatibility contract](references/OPENSPEC_MDG_PROFILE.feature.md)
10
+ in full before authoring or changing a compatible specification. Its Rules and
11
+ Scenarios specify the converter and demonstrate the format. Apply that structure
12
+ and its compatibility constraints to the requested domain's behavior. The contract
13
+ defines the supported mappings; this entry point explains how to use them.
14
+
15
+ ## Select the representation
16
+
17
+ | Input | Output | Command |
18
+ | --- | --- | --- |
19
+ | Markdown with Gherkin | OpenSpec | `opsx-gherkin mdg-to-opsx` |
20
+ | OpenSpec | Markdown with Gherkin | `opsx-gherkin opsx-to-mdg` |
21
+ | OpenSpec | Plain Gherkin | `opsx-gherkin opsx-to-feature` |
22
+
23
+ These are the supported conversion directions. Plain `.feature` is an output
24
+ and a directly validatable format; it is not a converter input.
25
+
26
+ After reading the contract, choose a complete example for the constructs you need:
27
+
28
+ - Basic main spec: [minimal.feature.md](references/examples/minimal.feature.md),
29
+ with a Feature description, a software target, one normative Rule, and a single-row Outline;
30
+ equivalent [OpenSpec](references/examples/minimal.openspec.md) and
31
+ [plain Gherkin](references/examples/minimal.feature).
32
+ - Shared setup, outlines, multiple Examples groups, Data Tables, and Doc Strings:
33
+ [account.feature.md](references/examples/account.feature.md), with
34
+ [OpenSpec](references/examples/account.openspec.md) and
35
+ [plain Gherkin](references/examples/account.feature) counterparts.
36
+ - Delta operations: [authentication-delta.feature.md](references/examples/authentication-delta.feature.md)
37
+ with [OpenSpec](references/examples/authentication-delta.openspec.md) and
38
+ [plain Gherkin](references/examples/authentication-delta.feature) counterparts.
39
+ This is a structural change document; each added/modified behavior still needs
40
+ a complete example, while removal/rename entries retain their restricted shape.
41
+ - Finding and retrieving the contract: [profile-access.feature.md](references/profile-access.feature.md).
42
+
43
+ These examples apply the canonical contract to their own domains.
44
+
45
+ ## Author within the contract
46
+
47
+ - Model each completed Feature as one whole public use case. Give each scenario
48
+ its own necessary prior facts, one action or query, and observable consequences.
49
+ Keep acceptance and rejection in separate scenarios of the same use case.
50
+ - Expose meaningful example values as named Outline arguments, even for one row.
51
+ Keep independent roles distinct, such as stored/submitted credentials and
52
+ requested/resulting quantities. Keep fixed criteria in Rules, cohesive records
53
+ in Data Tables, and literal source text intact when the use case examines it.
54
+ - Match the requested representation: Gherkin headings and bullet steps in MDG,
55
+ OpenSpec requirement headings and bold step keywords in OpenSpec, native
56
+ keyword lines in `.feature`. A fenced native document is not MDG structure.
57
+ - Use the contract's Feature/Purpose, Rule/Requirement, Background, and
58
+ Scenario mappings. Supply a meaningful main-spec Purpose and the required
59
+ `SHALL` or `MUST` wording for ordinary Rules and ADDED/MODIFIED requirements.
60
+ - Place descriptions before their child structures. Keep each Background in
61
+ its intended scope; preserve the original step roles and all example values.
62
+ - Use English structural keywords supported by this profile. Do not translate
63
+ keywords or rewrite domain content merely to make conversion succeed.
64
+ - Bind outline placeholders with concrete Examples rows. Keep Examples groups,
65
+ their tags, and step Data Tables distinct. Follow the contract's indented
66
+ Markdown table syntax and escaping. Check parsed row counts and literal
67
+ values, especially pipes, backslashes, newlines, and Doc String contents.
68
+ - Identify a completed Feature's verification boundary: `@code` for software,
69
+ `@skill` for an agent applying guidance, `@prompt` for a model executing a prompt,
70
+ or `@plugin` for its host integration. The software teaching examples use `@code`.
71
+ These tags identify the exercised boundary, not its internal dependencies.
72
+ Descriptive topic tags are optional and need not be copied from the contract.
73
+ - Conversion accepts compatible untagged documents and plain Scenarios too.
74
+ Preserve their existing model; do not insert targets or invent parameters
75
+ while converting. Authoring conventions do not narrow syntax acceptance.
76
+ - In MDG, put each tag in its own backtick span immediately above the tagged
77
+ heading. In native Gherkin, use bare tags. Reserve `@openspec-*` and `@mdg-*`
78
+ names for the structural roles defined by the contract.
79
+ - For deltas, mark the Feature and each operation as the contract specifies.
80
+ REMOVED supports the requirement name only. RENAMED supports its canonical
81
+ FROM and TO names only. Additional removal/rename content has no mapping;
82
+ report that boundary instead of silently deleting information.
83
+
84
+ ### Keep table and argument values intact
85
+
86
+ Indent Markdown table rows by two spaces. In MDG, put data rows directly after
87
+ the header, without a separator row; the converter emits this canonical form.
88
+ OpenSpec tables retain their Markdown separator row. The pinned MDG parser
89
+ accepts either input form and preserves the interpreted rows; table formatting
90
+ must not change Examples cases or Data Table values. Plain Gherkin also omits
91
+ separators. Use `\|` for a literal pipe, `\\` for a literal backslash,
92
+ and `\n` for a newline inside a cell. Do not apply cell escaping to Doc Strings.
93
+
94
+ Keep a Markdown Doc String in a triple-backtick block indented by two spaces
95
+ under its step; put its optional media type immediately after the opening fence.
96
+ Preserve the content's indentation relative to the fence. In an outline, each
97
+ `<placeholder>` names an Examples column; retain every intended data row and
98
+ verify the expanded cases. The account example demonstrates two separate
99
+ Examples groups and a JSON Doc String containing a placeholder.
100
+
101
+ ## Convert and verify
102
+
103
+ Each converter accepts one path, `--text`, or stdin. Use stdout to inspect a
104
+ result; use `-o <destination>` for an explicit output file. Existing outputs
105
+ require `--force`. Respect the user's destination and overwrite intent.
106
+
107
+ ```sh
108
+ opsx-gherkin mdg-to-opsx input.feature.md -o spec.md
109
+ opsx-gherkin opsx-to-mdg spec.md -o output.feature.md
110
+ opsx-gherkin opsx-to-feature spec.md -o output.feature
111
+ ```
112
+
113
+ Use `--name` when OpenSpec text has no title or usable source-path context.
114
+ `--project-root` supplies existing main-spec context for delta validation when
115
+ needed. Consult a converter's `--help` for its exact options.
116
+
117
+ The converters validate both endpoints and reject changes to the normalized
118
+ model before emitting output. Formatting may become canonical; names, values,
119
+ structure, and supported metadata must retain their meaning.
120
+
121
+ Direct endpoint validation checks syntax rather than conversion eligibility.
122
+ The standalone OpenSpec validator below accepts main specifications. For deltas,
123
+ use a supported conversion: it invokes the official strict delta validator and
124
+ checks model preservation.
125
+
126
+ ```sh
127
+ validate-spec --format openspec spec.md
128
+ validate-spec --format mdg input.feature.md
129
+ validate-spec --format feature output.feature
130
+ ```
131
+
132
+ Passing these checks establishes example syntax and conversion preservation.
133
+ Review the authored scenarios against the requested domain behavior as well;
134
+ validator success alone does not establish reliable agent authoring.
135
+
136
+ With npm/npx, use `npx opsx-gherkin <command>` for the main CLI and
137
+ `npx --package opsx-gherkin validate-spec ...` for the standalone validator.
138
+ `opsx-gherkin profile` prints the same contract linked above.
139
+
140
+ Exit status `1` means invalid input, unsupported profile content, or failed
141
+ conversion validation; `2` means usage or I/O failure. Read stderr and correct
142
+ the indicated issue without inventing a mapping or changing the intended
143
+ behavior. If the CLI is unavailable, explain the syntax from the contract and
144
+ state that execution has not been verified.
@@ -0,0 +1,372 @@
1
+ `@code` `@contract`
2
+ # Feature: Convert a compatible specification
3
+
4
+ This document is the normative contract for the conversion profile. The converters SHALL preserve the domain structure represented here and SHALL reject mappings not defined here.
5
+
6
+ Its Rules and Scenarios specify the converter, and their structure demonstrates valid Markdown with Gherkin. To author a specification for another domain, express that domain's requested behavior using the demonstrated structure and the compatibility rules in this complete contract.
7
+
8
+ The **@code** target identifies software as this Feature’s verification boundary. Descriptive tags such as **@contract** are optional example metadata. A new main specification may omit them; conversion of an existing document must preserve its tags. Markers in the reserved **@openspec-** and **@mdg-** namespaces have only the structural roles defined below.
9
+
10
+ The separate [profile-access use case](profile-access.feature.md) specifies how to find and retrieve this contract.
11
+
12
+ ## Background: Official validation
13
+
14
+ * Given the OpenSpec 1.11.0 strict validator is available
15
+ * And the Cucumber Gherkin 42.0.1 Markdown and plain-text parsers are available
16
+
17
+ ### Scenario: Preserve a scenario outside a Rule
18
+
19
+ * Given a valid feature-level scenario
20
+ * When it is converted to OpenSpec
21
+ * Then it is stored in the reserved **@mdg-feature-scenarios** Requirement
22
+
23
+ ### Scenario: Restore a scenario outside a Rule
24
+
25
+ * Given a compatible OpenSpec main spec containing the reserved **@mdg-feature-scenarios** Requirement
26
+ * When it is converted to MDG
27
+ * Then its scenarios become Feature-level scenarios
28
+ * And the reserved Requirement wrapper is removed
29
+
30
+ `@mapping`
31
+ ## Rule: Map document and requirement structure
32
+
33
+ The converter SHALL map a Gherkin Feature to an OpenSpec title and Purpose, and SHALL map every ordinary Rule to a Requirement with the same name and normative description.
34
+
35
+ ### Background: A valid normative Rule
36
+
37
+ * Given every ordinary Rule description contains `SHALL` or `MUST`
38
+
39
+ `@purpose`
40
+ ### Scenario: Map Feature metadata
41
+
42
+ * Given an MDG Feature with a name, description, and optional descriptive tags
43
+ * When it is converted to OpenSpec
44
+ * Then the name is written as the level-one title
45
+ * And the description is written under `## Purpose`
46
+ * And any supplied tags remain backticked tags adjacent to the title
47
+ * And an untagged Feature does not acquire illustrative tags from this contract
48
+
49
+ ### Scenario: Map Rules to Requirements
50
+
51
+ * Given the following structural correspondence:
52
+
53
+ | MDG | OpenSpec |
54
+ | Feature | title and Purpose |
55
+ | Rule | Requirement |
56
+ | Rule description | normative requirement text |
57
+ | Scenario | Scenario |
58
+ * When either representation is converted
59
+ * Then names, descriptions, tags, and child order are preserved
60
+ * And optional tags in a newly authored document do not permit dropping metadata from an existing source
61
+
62
+ ### Scenario: Map shared setup
63
+
64
+ * Given a compatible MDG main spec with both a Feature Background and a Rule Background
65
+ * When the specification is converted to OpenSpec
66
+ * Then it is written as a readable `**Background:**` block in the Requirements preamble
67
+ * And the Rule Background is written as `#### Background:` inside its Requirement
68
+
69
+ ### Scenario: Convert a complete sign-in specification to MDG
70
+
71
+ * Given this compatible OpenSpec document:
72
+
73
+ ```markdown
74
+ `@code`
75
+ # Sign in with account credentials
76
+
77
+ ## Purpose
78
+
79
+ Give an active account access when the submitted credentials match its registered credentials.
80
+
81
+ ## Requirements
82
+
83
+ **Background:**
84
+
85
+ - **GIVEN** the authentication service is available
86
+
87
+ `@credentials`
88
+ ### Requirement: Matching credentials grant access
89
+
90
+ The system SHALL grant access to an active account when submitted credentials match and SHALL reject a nonmatching password.
91
+
92
+ `@accepted`
93
+ #### Scenario Outline: Grant access for matching credentials
94
+
95
+ - **GIVEN** an active account has registered email `<registered_email>` and password `<registered_password>`
96
+ - **WHEN** a user signs in with email `<submitted_email>` and password `<submitted_password>`
97
+ - **THEN** access to the account registered as `<expected_email>` is granted
98
+
99
+ ##### Examples: Matching credentials
100
+
101
+ | registered_email | registered_password | submitted_email | submitted_password | expected_email |
102
+ | --- | --- | --- | --- | --- |
103
+ | user@example.com | correct-password | user@example.com | correct-password | user@example.com |
104
+
105
+ `@rejected`
106
+ #### Scenario Outline: Reject access for a nonmatching password
107
+
108
+ - **GIVEN** an active account has registered email `<registered_email>` and password `<registered_password>`
109
+ - **WHEN** a user signs in with email `<submitted_email>` and password `<submitted_password>`
110
+ - **THEN** access is rejected
111
+
112
+ ##### Examples: Nonmatching password
113
+
114
+ | registered_email | registered_password | submitted_email | submitted_password |
115
+ | --- | --- | --- | --- |
116
+ | user@example.com | correct-password | user@example.com | wrong-password |
117
+ ```
118
+ * When the document is converted to MDG
119
+ * Then the destination is:
120
+
121
+ ```markdown
122
+ `@code`
123
+ # Feature: Sign in with account credentials
124
+
125
+ Give an active account access when the submitted credentials match its registered credentials.
126
+
127
+ ## Background:
128
+
129
+ * Given the authentication service is available
130
+
131
+ `@credentials`
132
+ ## Rule: Matching credentials grant access
133
+
134
+ The system SHALL grant access to an active account when submitted credentials match and SHALL reject a nonmatching password.
135
+
136
+ `@accepted`
137
+ ### Scenario Outline: Grant access for matching credentials
138
+
139
+ * Given an active account has registered email `<registered_email>` and password `<registered_password>`
140
+ * When a user signs in with email `<submitted_email>` and password `<submitted_password>`
141
+ * Then access to the account registered as `<expected_email>` is granted
142
+
143
+ #### Examples: Matching credentials
144
+
145
+ | registered_email | registered_password | submitted_email | submitted_password | expected_email |
146
+ | user@example.com | correct-password | user@example.com | correct-password | user@example.com |
147
+
148
+ `@rejected`
149
+ ### Scenario Outline: Reject access for a nonmatching password
150
+
151
+ * Given an active account has registered email `<registered_email>` and password `<registered_password>`
152
+ * When a user signs in with email `<submitted_email>` and password `<submitted_password>`
153
+ * Then access is rejected
154
+
155
+ #### Examples: Nonmatching password
156
+
157
+ | registered_email | registered_password | submitted_email | submitted_password |
158
+ | user@example.com | correct-password | user@example.com | wrong-password |
159
+ ```
160
+
161
+ ## Rule: Preserve behavioral Gherkin constructs
162
+
163
+ The converter SHALL preserve Scenario and Scenario Outline structure, typed steps, tags, Examples, Data Tables, and Doc Strings without treating them as opaque prose.
164
+
165
+ Canonical MDG tables SHALL contain an indented header followed directly by data rows, without Markdown separator rows. Canonical OpenSpec tables SHALL retain their Markdown separator rows. Compatible MDG inputs with or without separators SHALL preserve the same interpreted rows and values.
166
+
167
+ ### Scenario: Map typed steps
168
+
169
+ * Given a Scenario containing Given, When, Then, And, and But steps
170
+ * When it is converted to OpenSpec
171
+ * Then each step uses a Markdown list item with its uppercase keyword in bold
172
+
173
+ ### Scenario: Restore English typed step roles
174
+
175
+ * Given a compatible OpenSpec Scenario containing bold GIVEN, WHEN, THEN, AND, and BUT list items
176
+ * When it is converted to MDG
177
+ * Then each step retains its corresponding English Gherkin keyword
178
+
179
+ `@outline`
180
+ ### Scenario Outline: Preserve `<expected_groups>` Examples groups in `<target_format>`
181
+
182
+ * Given a compatible `<source_format>` main spec with an Outline containing `<source_groups>` Examples groups
183
+ * When it is converted to `<target_format>`
184
+ * Then the Outline has `<expected_groups>` Examples groups
185
+ * And each group retains its name, tags, description, headers, and rows
186
+
187
+ `@single-group`
188
+ #### Examples: One group
189
+
190
+ | source_format | target_format | source_groups | expected_groups |
191
+ | MDG | OpenSpec | 1 | 1 |
192
+ | OpenSpec | MDG | 1 | 1 |
193
+
194
+ `@multiple-groups`
195
+ #### Examples: Several groups
196
+
197
+ | source_format | target_format | source_groups | expected_groups |
198
+ | OpenSpec | MDG | 3 | 3 |
199
+ | MDG | OpenSpec | 3 | 3 |
200
+
201
+ ### Scenario Outline: Preserve literal step arguments in `<target_format>`
202
+
203
+ * Given a compatible `<source_format>` main spec with a scenario step containing this Data Table:
204
+
205
+ | field | value |
206
+ | status | active |
207
+ | attempts | 4 |
208
+ * And another step in the same scenario containing this Doc String:
209
+
210
+ ```json
211
+ {
212
+ "error": "invalid_credentials"
213
+ }
214
+ ```
215
+ * When the document is converted to `<target_format>`
216
+ * Then table cells, Doc String media type, and Doc String content are unchanged
217
+
218
+ #### Examples: Argument conversion directions
219
+
220
+ | source_format | target_format |
221
+ | MDG | OpenSpec |
222
+ | OpenSpec | MDG |
223
+
224
+ ## Rule: Project OpenSpec to a plain Gherkin feature
225
+
226
+ The `opsx-to-feature` command SHALL preserve the same normalized behavioral model as `opsx-to-mdg` and MUST emit plain-text Gherkin accepted by the official parser.
227
+
228
+ ### Scenario: Render plain Gherkin syntax
229
+
230
+ * Given a valid OpenSpec main specification
231
+ * When `opsx-to-feature` converts it
232
+ * Then Feature, Rule, Background, Scenario, Scenario Outline, Examples, steps, Data Tables, Doc Strings, descriptions, and tags use plain Gherkin syntax
233
+ * And structural Markdown headings, list markers, backticked tags, and table separator rows are absent
234
+ * And the output is suitable for a `.feature` file
235
+
236
+ ### Scenario: Render an OpenSpec delta as Gherkin
237
+
238
+ * Given a valid OpenSpec delta
239
+ * When `opsx-to-feature` converts it
240
+ * Then the Feature and Rules retain the reserved delta tags defined by this contract
241
+ * And RENAMED retains its canonical FROM and TO requirement names
242
+
243
+ ## Rule: Represent OpenSpec deltas idiomatically in MDG
244
+
245
+ The converter SHALL mark a delta Feature with **@openspec-delta** and SHALL represent each delta operation as a Rule carrying exactly one reserved operation tag.
246
+
247
+ ### Scenario Outline: Map every delta operation
248
+
249
+ * Given an OpenSpec `<operation>` requirement
250
+ * When it is converted to MDG
251
+ * Then its Rule carries `<tag>`
252
+
253
+
254
+ #### Examples: Supported operations
255
+
256
+ | operation | tag |
257
+ | ADDED | @openspec-added |
258
+ | MODIFIED | @openspec-modified |
259
+ | REMOVED | @openspec-removed |
260
+ | RENAMED | @openspec-renamed |
261
+
262
+ ### Scenario Outline: Restore the `<operation>` section from its reserved marker
263
+
264
+ * Given a compatible MDG delta with a Rule carrying `<tag>` and the content required for that operation
265
+ * When the delta is converted to OpenSpec
266
+ * Then the entry belongs to the `<operation>` section
267
+ * And its requirement identity and supported content are preserved
268
+
269
+ #### Examples: Operation identities
270
+
271
+ | operation | tag |
272
+ | ADDED | @openspec-added |
273
+ | MODIFIED | @openspec-modified |
274
+ | REMOVED | @openspec-removed |
275
+ | RENAMED | @openspec-renamed |
276
+
277
+ ### Scenario Outline: Preserve the rename from `<old_name>` to `<new_name>`
278
+
279
+ * Given a compatible OpenSpec delta renaming the requirement `<old_name>` to `<new_name>`
280
+ * When the delta is converted to MDG
281
+ * Then the Rule description contains canonical FROM and TO lines:
282
+
283
+ ```text
284
+ FROM: `### Requirement: <old_name>`
285
+ TO: `### Requirement: <new_name>`
286
+ ```
287
+
288
+ #### Examples: Renamed requirements
289
+
290
+ | old_name | new_name |
291
+ | Login audit | Authentication audit |
292
+
293
+ ## Rule: Validate and normalize every conversion
294
+
295
+ The converter SHALL validate both endpoints with their official deterministic validators and MUST emit only a canonical result whose normalized model equals the input model.
296
+
297
+ ### Scenario: Complete a successful conversion
298
+
299
+ * Given a source document accepted by its official validator and this profile
300
+ * When either converter runs
301
+ * Then the generated document is accepted by the destination validator
302
+ * And parsing the generated document produces the same normalized model
303
+
304
+ ### Scenario Outline: Reject a Rule without normative wording
305
+
306
+ * Given a syntactically valid MDG main spec whose ordinary Rule description contains neither SHALL nor MUST
307
+ * When conversion to OpenSpec is requested
308
+ * Then the command exits with status `<exit_status>`
309
+ * And no destination document is emitted
310
+ * And diagnostics identify the Rule that lacks normative wording
311
+
312
+ #### Examples: Invalid profile outcome
313
+
314
+ | exit_status |
315
+ | 1 |
316
+
317
+ ### Scenario: Reject removal metadata without a defined mapping
318
+
319
+ * Given a REMOVED Requirement or Rule containing prose, a Background, or scenarios
320
+ * When conversion is attempted
321
+ * Then the command reports that REMOVED supports only the requirement name
322
+ * And no content is silently discarded
323
+
324
+ ### Scenario: Reject extra rename content
325
+
326
+ * Given a RENAMED Rule containing content beyond its canonical FROM and TO names
327
+ * When conversion is attempted
328
+ * Then the command reports the unsupported content
329
+ * And no destination document is emitted
330
+
331
+ ### Scenario Outline: Preserve delta group order in `<target_format>`
332
+
333
+ * Given a compatible `<source_format>` delta with its MODIFIED group before its ADDED group
334
+ * When the delta is converted to `<target_format>`
335
+ * Then MODIFIED still precedes ADDED
336
+ * And requirement order within each group is unchanged
337
+
338
+ #### Examples: Ordered delta conversion directions
339
+
340
+ | source_format | target_format |
341
+ | OpenSpec | MDG |
342
+ | MDG | OpenSpec |
343
+
344
+ ## Rule: Support symmetric command input and output
345
+
346
+ All converter commands SHALL accept one path, inline text, or stdin and SHALL return text on stdout or write one destination path atomically.
347
+
348
+ ### Scenario Outline: Select an input source
349
+
350
+ * Given input is supplied through `<source>`
351
+ * When the converter starts
352
+ * Then that source is read without guessing whether literal text is a path
353
+
354
+ #### Examples: Input modes
355
+
356
+ | source |
357
+ | positional path |
358
+ | --text |
359
+ | stdin |
360
+
361
+ ### Scenario Outline: Preserve an existing destination without overwrite permission
362
+
363
+ * Given a compatible MDG source is available
364
+ * And the destination `<destination>` already contains a document
365
+ * When conversion to OpenSpec is requested at `<destination>` without `--force`
366
+ * Then the command exits with status `<exit_status>`
367
+ * And the bytes in `<destination>` remain unchanged
368
+
369
+ #### Examples: Protected destination
370
+
371
+ | destination | exit_status |
372
+ | account.spec.md | 2 |
@@ -0,0 +1,85 @@
1
+ @code @authentication @security
2
+ Feature: User sign-in
3
+
4
+ Allow registered users to access their accounts securely while rejecting invalid credentials and limiting repeated attempts.
5
+
6
+ Background:
7
+ Given the authentication service is available
8
+
9
+ @credentials
10
+ Rule: Credentials are verified securely
11
+
12
+ The system SHALL grant access only when an active registered user provides the correct email address and password.
13
+
14
+ @smoke @positive
15
+ Scenario Outline: Sign-in succeeds with correct credentials
16
+ Given an active registered account with these credentials:
17
+ | email | password |
18
+ | <storedEmail> | <storedPassword> |
19
+ And the account has <priorFailureCount> consecutive failed sign-in attempts
20
+ When the user signs in with email "<submittedEmail>" and password "<submittedPassword>"
21
+ Then the system grants access to the account with email "<expectedAccountEmail>"
22
+ And the system starts an authenticated session
23
+
24
+ Examples: Matching credentials
25
+ | storedEmail | storedPassword | priorFailureCount | submittedEmail | submittedPassword | expectedAccountEmail |
26
+ | user@example.com | correct-password | 0 | user@example.com | correct-password | user@example.com |
27
+
28
+ @negative @api
29
+ Scenario Outline: Sign-in rejects invalid credentials
30
+ Given only the following active account is registered:
31
+ | email | password |
32
+ | <storedEmail> | <storedPassword> |
33
+ And the account has <priorFailureCount> consecutive failed sign-in attempts
34
+ When a user signs in with email "<submittedEmail>" and password "<submittedPassword>"
35
+ Then the system rejects access with HTTP status <httpStatus>
36
+ And the response body is:
37
+ """json
38
+ {
39
+ "error": "<errorCode>"
40
+ }
41
+ """
42
+
43
+ @unknown-account
44
+ Examples: Unknown account
45
+ | storedEmail | storedPassword | priorFailureCount | submittedEmail | submittedPassword | httpStatus | errorCode |
46
+ | user@example.com | correct-password | 0 | missing@example.com | any-password | 401 | invalid_credentials |
47
+
48
+ @incorrect-password
49
+ Examples: Incorrect password
50
+ | storedEmail | storedPassword | priorFailureCount | submittedEmail | submittedPassword | httpStatus | errorCode |
51
+ | user@example.com | correct-password | 0 | user@example.com | wrong-password | 401 | invalid_credentials |
52
+
53
+ @account-protection
54
+ Rule: Repeated failures lock the account
55
+
56
+ The system SHALL lock an account after five consecutive failed sign-in attempts. The system SHALL reject sign-in while the account is locked, even when the submitted credentials are correct.
57
+
58
+ @negative @lockout
59
+ Scenario Outline: Sign-in locks the account at the failure limit
60
+ Given an active registered account with these credentials:
61
+ | email | password |
62
+ | <storedEmail> | <storedPassword> |
63
+ And the account has <priorFailureCount> consecutive failed sign-in attempts
64
+ When a user signs in with email "<submittedEmail>" and password "<submittedPassword>"
65
+ Then the system rejects access
66
+ And the account has <expectedFailureCount> consecutive failed sign-in attempts
67
+ And the system locks the account
68
+
69
+ Examples: Failure limit reached
70
+ | storedEmail | storedPassword | priorFailureCount | submittedEmail | submittedPassword | expectedFailureCount |
71
+ | user@example.com | correct-password | 4 | user@example.com | wrong-password | 5 |
72
+
73
+ @negative @lockout
74
+ Scenario Outline: A locked account rejects correct credentials
75
+ Given a registered account with these credentials:
76
+ | email | password |
77
+ | <storedEmail> | <storedPassword> |
78
+ And the account has <priorFailureCount> consecutive failed sign-in attempts
79
+ But the account is locked
80
+ When a user signs in with email "<submittedEmail>" and password "<submittedPassword>"
81
+ Then the system rejects access
82
+
83
+ Examples: Correct credentials while locked
84
+ | storedEmail | storedPassword | priorFailureCount | submittedEmail | submittedPassword |
85
+ | user@example.com | correct-password | 5 | user@example.com | correct-password |