Skip to content
CLI docs

Python API

Programmatic access to skene's codebase exploration, configuration, LLM client, and journey models.

Quick example

from pathlib import Path
import yaml

from skene import CodebaseExplorer
from skene.analyzers.journey.models import Journey

# Sandboxed access to a codebase
explorer = CodebaseExplorer(Path("/path/to/repo"))

# Load and validate a journey.yaml produced by `skene analyse-journey`
journey = Journey.model_validate(
    yaml.safe_load(Path("skene-context/journey.yaml").read_text())
)
print(journey.product.name)
for stage in journey.stages:
    print(stage.name, [m.name for m in stage.milestones])

CodebaseExplorer

Safe, sandboxed access to codebase files. Automatically excludes common build/cache directories.

from pathlib import Path
from skene import CodebaseExplorer, DEFAULT_EXCLUDE_FOLDERS

# Create with default exclusions
explorer = CodebaseExplorer(Path("/path/to/repo"))

# Create with custom exclusions (merged with defaults)
explorer = CodebaseExplorer(
    Path("/path/to/repo"),
    exclude_folders=["tests", "vendor", "migrations"]
)

Methods

MethodReturnsDescription
await get_directory_tree(start_path, max_depth)dictDirectory tree with file counts
await search_files(start_path, pattern)dictFiles matching glob pattern
await read_file(file_path)strFile contents
await read_multiple_files(file_paths)dictMultiple file contents
should_exclude(path)boolCheck if a path should be excluded
  • build_directory_tree: Standalone function for building directory trees
  • DEFAULT_EXCLUDE_FOLDERS: List of default excluded folder names

Configuration

from skene import Config, load_config

# Load config from files + env vars
config = load_config()

# Access properties
config.api_key       # str | None
config.provider      # str (default: "openai")
config.model         # str (auto-determined if not set)
config.output_dir    # str (default: "./skene-context"; legacy "./skene" auto-detected)
config.debug         # bool (default: False)
config.exclude_folders  # list[str] (default: [])
config.base_url      # str | None
config.upstream      # str | None (upstream workspace URL)

# Get/set arbitrary keys
config.get("api_key", default=None)
config.set("provider", "gemini")

Upstream credentials

from skene.config import (
    save_upstream_to_config,    # Save upstream URL, workspace, API key to .skene.config
    remove_upstream_from_config,# Remove upstream credentials from .skene.config
    resolve_upstream_token,     # Resolve token from env/config
)

LLM Client

from pydantic import SecretStr
from skene.llm import create_llm_client, LLMClient

client: LLMClient = create_llm_client(
    provider="openai",          # openai, gemini, anthropic, ollama, lmstudio, generic
    api_key=SecretStr("key"),
    model="gpt-4o",
    base_url=None,              # Required for generic provider
    debug=False,                # Log LLM I/O to ~/.local/state/skene/debug/
)

Journey models

The journey.yaml schema is defined by Pydantic v2 models in skene.analyzers.journey.models. They are validated end-to-end before the file is written by skene analyse-journey.

from skene.analyzers.journey.models import (
    Journey,        # The whole document
    Product,        # Product metadata (name, description, generated_at, source_commit)
    Stage,          # A lifecycle stage containing milestones and KPIs
    Milestone,      # A user-facing milestone with evidence
    Kpi,            # A stage KPI
    KpiDerivation,  # How a KPI is derived from tables/events
    Layer,          # A named layer spanning multiple stages
    Connector,      # A cross-stage link between milestones
    Evidence,       # Re-exported from skene.schema.feature
    EvidenceSource, # Re-exported from skene.schema.feature
    TriggerType,    # Enum: email, scheduled, webhook, event_bus, unknown
    ConnectorStyle, # Enum: solid, dashed, dotted
    KpiUnit,        # Enum: percentage, count, duration_days, duration_hours, ratio, currency
)

Journey fields

FieldType
productProduct
layerslist[Layer]
stageslist[Stage] (min 1)
connectorslist[Connector]

Model validators enforce unique stage/layer/connector IDs, unique stage orders, and that layers and connectors reference real stages/milestones.

Stage fields

FieldType
idstr (snake_case ID)
orderint (>= 1)
namestr
subtitlestr | None
milestoneslist[Milestone] (min 1, unique IDs and orders)
kpislist[Kpi] (unique IDs)

Milestone fields

FieldType
idstr (snake_case ID)
orderint (>= 1)
namestr
descriptionstr
evidencelist[Evidence] (min 1)
tracked_eventstr | None
confidencefloat (0.0–1.0, default 1.0)

Connector fields

FieldType
idstr (snake_case ID)
fromstr ("<stage_id>.<milestone_id>")
tostr (milestone ref or the literal "unknown")
labelstr
trigger_typeTriggerType
styleConnectorStyle (default dashed)
confidencefloat (0.0–1.0, default 1.0)
evidencelist[Evidence] (min 1)

Serialization

from skene.analyzers.journey import serialize

yaml_text = serialize.to_yaml(journey)   # Render Journey to YAML
json_text = serialize.to_json(journey)   # Render Journey to JSON
serialize.write(journey, path)           # Write journey.yaml to disk

Journey pipeline

The rest of the journey machinery lives alongside the models and is orchestrated by skene.analyzers.journey.pipeline.run_journey_pipeline, which runs six steps: specialize stages, the schema agent and the code agent in parallel, a deterministic merge, per-milestone classification, then assembly.

  • skene.analyzers.journey.specialize: specialize_stages adapts the canonical stages to the product, or is skipped for the canonical set
  • skene.analyzers.journey.schema_agent / code_agent: run_schema_agent and run_code_agent each propose CandidateMilestone objects, in parallel
  • skene.analyzers.journey.merge: merge_candidates reconciles the two candidate sets by exact id, then by fuzzy name. No model in the loop
  • skene.analyzers.journey.classify: classify_milestone / classify_all assign each merged candidate to a stage
  • skene.analyzers.journey.assemble: assemble_journey builds the final validated Journey
  • skene.analyzers.journey.serialize: write persists it to skene-context/journey.yaml
  • skene.analyzers.schema_parsers: parse_schema_dir (SQL files) and introspect_db (live PostgreSQL) produce the schema input

Feature registry

from skene.feature_registry import (
    load_feature_registry,              # Load registry from disk
    write_feature_registry,             # Write registry to disk
    merge_features_into_registry,       # Merge new features with existing registry
    upsert_registry_from_engine,        # Upsert registry entries from engine.yaml features
    export_registry_to_format,          # Export to json, csv, or markdown
    derive_feature_id,                  # Convert feature name to snake_case ID
    compute_loop_ids_by_feature,        # Map feature_id -> list of loop_ids
)

Key functions

FunctionDescription
merge_features_into_registry(new_features, registry)Merges new features: adds new, updates matched, archives missing
upsert_registry_from_engine(engine_doc, registry_path)Upserts feature-registry entries from engine.yaml features
export_registry_to_format(registry, format)Exports to "json", "csv", or "markdown"

Engine and migrations

from skene.engine import (
    load_engine_document,               # Load engine.yaml from the bundle dir
    write_engine_document,              # Write engine.yaml to the bundle dir
    merge_engine_documents,             # Merge delta by key
    parse_source_to_db_event,           # Parse schema.table.operation source
    engine_features_to_loop_definitions # Adapter for migration builder
)

from skene.growth_loops.push import (
    ensure_base_schema_migration,       # Check, build, update base schema (creates or overwrites)
    build_loops_to_supabase,            # Build Supabase migrations from trigger definitions
    build_migration_sql,                # Generate migration SQL
    find_trigger_migration,             # Latest telemetry migration path (*_skene_triggers.sql + legacy names)
    write_migration,                    # Write timestamped *_skene_triggers.sql (default migration_name)
    push_to_upstream,                   # Push to upstream API
)

from skene.growth_loops.upstream import (
    validate_token,                     # Validate token via upstream API
    collect_push_files,                 # [{path, content}]: full bundle under output_dir + trigger SQL
    build_push_manifest,                # Create push manifest with checksum over files
    push_to_upstream,                   # POST {manifest, files} to /api/v1/push
)