-
Notifications
You must be signed in to change notification settings - Fork 1
Description
Summary
Convert a list[DraftFeature] into SpecLeft-format markdown files written to .specleft/specs/discovered/. Reuses existing feature_writer.py utilities for ID generation and validation. Uses SpecStep objects from DraftScenario directly — no lossy string round-trip.
New file
src/specleft/discovery/spec_writer.py
from specleft.discovery.models import DraftFeature, DraftScenario
from specleft.schema import SpecStep, StepType
def generate_draft_specs(
draft_features: list[DraftFeature],
output_dir: Path,
dry_run: bool = False,
overwrite: bool = False,
) -> list[Path]:
"""
Returns list of written file paths (or would-be paths if dry_run=True).
Skips existing files unless overwrite=True.
Creates output_dir if it does not exist (unless dry_run).
"""Output format per file
# Feature: User Authentication
<!-- generated by specleft discover — review before promoting to .specleft/specs/ -->
## Scenarios
### Scenario: valid-credentials
priority: medium
<!-- source: tests/auth/test_login.py:14 -->
- Given a user with valid credentials
- When they attempt to login
- Then they should be authenticated
### Scenario: expired-token
priority: medium
<!-- source: tests/auth/test_login.py:28 -->
- Given a user with an expired token
- When they attempt an authenticated request
- Then they should receive a 401 responseStep generation from SpecStep objects
DraftScenario.steps is list[SpecStep] — no string parsing needed. The writer serialises each step directly:
for step in scenario.steps:
# step.type is StepType (GIVEN, WHEN, THEN, AND, BUT)
# step.description is the text
line = f"- {step.type.value} {step.description}"Miners and the grouping algorithm are responsible for constructing SpecStep objects. The step generation rules per ItemKind remain:
| Kind | Given | When | Then |
|---|---|---|---|
TEST_FUNCTION |
context from name tokens before first verb | verb token as action | remainder as outcome |
API_ROUTE |
"a valid request" |
"{METHOD} {path} is called" |
"a response is returned" |
DOCSTRING |
first sentence of raw_text |
second sentence | third sentence; pad to 3 |
GIT_COMMIT |
"the system is in a known state" |
commit subject as action | "the expected outcome occurs" |
These rules are applied when constructing DraftScenario objects (in grouping.py or a helper), not in the writer.
Parser exclusion — _discovered/ convention
The staging directory should use an underscore prefix convention to distinguish it from user-created spec directories:
Default staging path: .specleft/specs/_discovered/
Update src/specleft/parser.py to skip directories whose name starts with _ when recursing. This is consistent with Python's _private convention and prevents naming collisions with user features:
_discovered/→ skipped by parser_drafts/→ would also be skipped (future-proof)discovered/→ would NOT be skipped (user could name a feature this)
Note: This changes the staging path from .specleft/specs/discovered/ to .specleft/specs/_discovered/. Update all references in #124 (DraftSpec.output_dir), #136 (discover command), and #137 (start command).
Utilities to reuse
src/specleft/utils/feature_writer.py→generate_feature_id(),generate_scenario_id(),validate_feature_id(),validate_scenario_id()
Acceptance criteria
-
dry_run=Truereturns correct file paths without writing any files - Generated markdown parses successfully with the existing
SpecParserafter promotion (zero validation errors) - Steps are serialised directly from
SpecStepobjects — no string→parse round-trip - Each scenario has exactly 3 steps (Given / When / Then)
-
feature_idandscenario_idpassvalidate_feature_id()andvalidate_scenario_id() - Existing file is not overwritten when
overwrite=False(default) -
overwrite=Truereplaces the existing file -
SpecsConfig.from_directory(".specleft/specs/")does NOT load files from.specleft/specs/_discovered/ - Parser skips all directories whose name starts with
_when recursing - Tests in
tests/discovery/test_spec_writer.py - Update scenarios and tests in
features/feature-spec-discovery.mdto cover the functionality introduced by this issue