orchestrata_ultimate.openspec.detect
Reference › orchestrata_ultimate › openspec › detect
detect locates a project's spec folder and recognises its format (SPEC-104 FR-001 and FR-002). OpenSpec lives in an openspec/ directory, while orchestrata's own specs live in specs/NNN-*/spec.md. detect_spec_folder takes a project root and returns a SpecFolder from models whose format is openspec, orchestrata or unknown, with its capabilities and changes listed (_subdir_names). _first_spec_md reads the first spec.md under a directory at any depth, and is_openspec decides by matching OpenSpec markers (requirement and scenario headers, or a delta header) against orchestrata markers such as REQ-NNN- ids. Detection never raises: an absent or unrecognised folder yields format unknown. The package imports it.
At a glance
| Source | src/orchestrata_ultimate/openspec/detect.py |
| Lines of code | 94 |
| Classes | 0 |
| Functions | 4 |
| Imports (internal) | orchestrata_ultimate.core.base.traces, orchestrata_ultimate.openspec.models |
| Imported by | orchestrata_ultimate.openspec |
| Tests that import it | none |
Class diagram
No classes; see Functions.
Dependency diagram
API
Functions
| Function | Signature | Summary |
|---|---|---|
is_openspec traces |
is_openspec(text: str) -> bool |
True if the markdown uses OpenSpec structure (Requirement+Scenario, or a delta). |
_first_spec_md traces |
_first_spec_md(specs_dir: Path) -> str |
Read the first spec.md under specs_dir (any nesting), or '' if none. |
detect_spec_folder traces |
detect_spec_folder(root: str | Path) -> SpecFolder |
Locate the spec folder under root and classify its format (FR-001/002). |
_subdir_names traces |
_subdir_names(directory: Path) -> list[str] |
Source
"""Locate a project's spec folder and recognize its format (SPEC-104 FR-001/002).
OpenSpec lives in an ``openspec/`` directory; orchestrata's own specs live in
``specs/NNN-*/spec.md``. Detection never raises — an absent or unrecognized
folder yields a ``SpecFolder`` with format ``"unknown"``.
"""
from __future__ import annotations
import re
from pathlib import Path
from orchestrata_ultimate.core.base.traces import traces
from orchestrata_ultimate.openspec.models import SpecFolder
# Distinctive OpenSpec markers (case-sensitive headers, per the convention).
_OPENSPEC_REQUIREMENT = re.compile(r"^### Requirement:", re.MULTILINE)
_OPENSPEC_SCENARIO = re.compile(r"^#### Scenario:", re.MULTILINE)
_OPENSPEC_DELTA = re.compile(
r"^## (?:ADDED|MODIFIED|REMOVED|RENAMED) Requirements", re.MULTILINE
)
# orchestrata SPEC-NNN markers.
_ORCHESTRATA = re.compile(r"REQ-\d{3}-|^### FR-\d|^- \*\*Spec ID\*\*", re.MULTILINE)
@traces(spec="104", story="US-104-001", reqs=["REQ-104-002"])
def is_openspec(text: str) -> bool:
"""True if the markdown uses OpenSpec structure (Requirement+Scenario, or a delta)."""
if _OPENSPEC_DELTA.search(text):
return True
return bool(_OPENSPEC_REQUIREMENT.search(text) and _OPENSPEC_SCENARIO.search(text))
@traces(spec="104", story="US-104-001", reqs=["REQ-104-002", "REQ-104-003"])
def _first_spec_md(specs_dir: Path) -> str:
"""Read the first spec.md under specs_dir (any nesting), or '' if none."""
for spec in sorted(specs_dir.rglob("spec.md")):
try:
return spec.read_text(encoding="utf-8", errors="replace")
except OSError:
continue
return ""
@traces(
spec="104",
story="US-104-001",
reqs=["REQ-104-001", "REQ-104-002", "REQ-104-003"],
)
def detect_spec_folder(root: str | Path) -> SpecFolder:
"""Locate the spec folder under root and classify its format (FR-001/002).
Prefers an ``openspec/`` directory; otherwise inspects a ``specs/`` folder and
sniffs a spec.md to decide openspec vs orchestrata. Returns a SpecFolder with
``format="unknown"`` and ``root=None`` when nothing is found.
"""
base = Path(root)
openspec_dir = base / "openspec"
if openspec_dir.is_dir():
specs = openspec_dir / "specs"
changes = openspec_dir / "changes"
return SpecFolder(
root=openspec_dir,
format="openspec",
capabilities=_subdir_names(specs),
changes=[c for c in _subdir_names(changes) if c != "archive"],
)
specs_dir = base / "specs"
if specs_dir.is_dir():
sample = _first_spec_md(specs_dir)
if is_openspec(sample):
return SpecFolder(
root=specs_dir, format="openspec", capabilities=_subdir_names(specs_dir)
)
if _ORCHESTRATA.search(sample):
return SpecFolder(
root=specs_dir,
format="orchestrata",
capabilities=_subdir_names(specs_dir),
)
return SpecFolder(root=specs_dir, format="unknown")
return SpecFolder(root=None, format="unknown")
@traces(spec="104", story="US-104-001", reqs=["REQ-104-001"])
def _subdir_names(directory: Path) -> list[str]:
if not directory.is_dir():
return []
return sorted(
d.name for d in directory.iterdir() if d.is_dir() and not d.name.startswith(".")
)