Skip to content

Spec Lifecycle API

The Lifecycle module provides tools for managing specification health, pruning, generation, and compression.

Import

from specmem.lifecycle import (
    HealthAnalyzer,
    PrunerEngine,
    GeneratorEngine,
    CompressorEngine,
    SpecHealthScore,
    PruneResult,
    GeneratedSpec,
    CompressedSpec,
)

HealthAnalyzer

Analyzes specification health based on code references, modification dates, and usage patterns.

Constructor

HealthAnalyzer(
    spec_base_path: Path,
    stale_threshold_days: int = 90,
)

Methods

analyze_spec

Analyze health of a single specification.

def analyze_spec(
    spec_name: str,
    spec_path: Path,
) -> SpecHealthScore

analyze_all

Analyze health of all specifications.

def analyze_all() -> list[SpecHealthScore]

get_orphaned_specs

Get specs with no code references.

def get_orphaned_specs() -> list[SpecHealthScore]

get_stale_specs

Get specs not modified within threshold.

def get_stale_specs(threshold_days: int | None = None) -> list[SpecHealthScore]

Example

from pathlib import Path
from specmem.lifecycle import HealthAnalyzer

analyzer = HealthAnalyzer(
    spec_base_path=Path(".kiro/specs"),
    stale_threshold_days=90,
)

# Analyze all specs
scores = analyzer.analyze_all()
for score in scores:
    print(f"{score.spec_name}: {score.score:.2f}")
    if score.is_orphaned:
        print("  ⚠️ Orphaned - no code references")

# Get summary
summary = analyzer.get_summary()
print(f"Average health: {summary['average_score']:.2f}")

PrunerEngine

Prunes orphaned or stale specifications with archive/delete modes.

Constructor

PrunerEngine(
    health_analyzer: HealthAnalyzer,
    archive_dir: Path | None = None,
)

Methods

analyze

Identify specs that can be pruned.

def analyze() -> list[PruneResult]

prune_orphaned

Prune all orphaned specifications.

def prune_orphaned(
    mode: Literal["archive", "delete"] = "archive",
    dry_run: bool = True,
    force: bool = False,
) -> list[PruneResult]

prune_stale

Prune stale specifications.

def prune_stale(
    threshold_days: int = 90,
    mode: Literal["archive", "delete"] = "archive",
    dry_run: bool = True,
) -> list[PruneResult]

prune_by_name

Prune specific specs by name.

def prune_by_name(
    spec_names: list[str],
    mode: Literal["archive", "delete"] = "archive",
    dry_run: bool = True,
    force: bool = False,
) -> list[PruneResult]

Example

from pathlib import Path
from specmem.lifecycle import HealthAnalyzer, PrunerEngine

analyzer = HealthAnalyzer(Path(".kiro/specs"))
pruner = PrunerEngine(
    health_analyzer=analyzer,
    archive_dir=Path(".specmem/archive"),
)

# Preview orphaned specs
results = pruner.prune_orphaned(dry_run=True)
for r in results:
    print(f"Would {r.action}: {r.spec_name}")

# Actually archive them
results = pruner.prune_orphaned(dry_run=False)

GeneratorEngine

Generates specifications from existing code files.

Constructor

GeneratorEngine(
    default_format: str = "kiro",
    output_dir: Path | None = None,
)

Methods

generate_from_file

Generate spec from a single code file.

def generate_from_file(
    file_path: Path,
    output_format: str | None = None,
) -> GeneratedSpec

generate_from_directory

Generate specs from a directory.

def generate_from_directory(
    dir_path: Path,
    group_by: Literal["file", "directory", "module"] = "directory",
    output_format: str | None = None,
) -> list[GeneratedSpec]

write_spec

Write generated spec to disk.

def write_spec(spec: GeneratedSpec) -> Path

Example

from pathlib import Path
from specmem.lifecycle import GeneratorEngine

generator = GeneratorEngine(
    default_format="kiro",
    output_dir=Path(".kiro/specs"),
)

# Generate from file
spec = generator.generate_from_file(Path("src/auth/service.py"))
print(f"Generated: {spec.name}")
print(f"Functions: {len(spec.functions)}")

# Generate from directory
specs = generator.generate_from_directory(
    Path("src/"),
    group_by="module",
)

# Write to disk
for spec in specs:
    path = generator.write_spec(spec)
    print(f"Wrote: {path}")

CompressorEngine

Compresses verbose specifications while preserving acceptance criteria.

Constructor

CompressorEngine(
    verbose_threshold_chars: int = 5000,
    compression_storage_dir: Path | None = None,
)

Methods

compress_spec

Compress a single specification.

def compress_spec(
    spec_name: str,
    spec_path: Path,
) -> CompressedSpec

compress_all

Compress all verbose specifications.

def compress_all(
    specs: list[tuple[str, Path]],
) -> list[CompressedSpec]

get_verbose_specs

Identify verbose specs above threshold.

def get_verbose_specs(
    specs: list[tuple[str, Path]],
    threshold: int | None = None,
) -> list[str]

save_compressed

Save compressed version to storage.

def save_compressed(compressed: CompressedSpec) -> Path

Example

from pathlib import Path
from specmem.lifecycle import CompressorEngine

compressor = CompressorEngine(
    verbose_threshold_chars=5000,
    compression_storage_dir=Path(".specmem/compressed"),
)

# Find verbose specs
spec_base = Path(".kiro/specs")
specs = [(d.name, d) for d in spec_base.iterdir() if d.is_dir()]

verbose = compressor.get_verbose_specs(specs)
print(f"Verbose specs: {verbose}")

# Compress a spec
compressed = compressor.compress_spec("auth-feature", spec_base / "auth-feature")
print(f"Original: {compressed.original_chars} chars")
print(f"Compressed: {compressed.compressed_chars} chars")
print(f"Ratio: {compressed.compression_ratio:.1%}")

# Save compressed version
path = compressor.save_compressed(compressed)

Data Models

SpecHealthScore

@dataclass
class SpecHealthScore:
    spec_name: str
    spec_path: Path
    score: float              # 0.0 to 1.0
    is_orphaned: bool         # No code references
    is_stale: bool            # Not modified recently
    last_modified: datetime
    code_references: int      # Number of code files referencing
    query_count: int          # Times queried
    recommendations: list[str]

PruneResult

@dataclass
class PruneResult:
    spec_name: str
    action: str               # "archive", "delete", "skip"
    reason: str               # Why this action
    archive_path: Path | None # Where archived (if applicable)
    dry_run: bool             # Was this a preview
    success: bool

GeneratedSpec

@dataclass
class GeneratedSpec:
    name: str
    source_path: Path
    format: str               # "kiro", "speckit"
    content: str              # Generated spec content
    functions: list[str]      # Extracted function names
    classes: list[str]        # Extracted class names
    auto_generated: bool      # Always True
    timestamp: datetime

CompressedSpec

@dataclass
class CompressedSpec:
    spec_name: str
    original_content: str
    compressed_content: str
    original_chars: int
    compressed_chars: int
    compression_ratio: float
    preserved_criteria: list[str]  # Acceptance criteria kept

Client Integration

The lifecycle features are also available through SpecMemClient:

from specmem import SpecMemClient

sm = SpecMemClient()

# Health analysis
health = sm.get_spec_health()
print(f"Average: {health['average_score']:.2f}")

# Prune orphaned specs
results = sm.prune_specs(orphaned=True, dry_run=True)

# Generate specs from code
specs = sm.generate_specs(["src/auth/"], write=True)

# Compress verbose specs
compressed = sm.compress_specs(all_verbose=True, save=True)

See Client API for full method documentation.