Dev Module (stx.dev)

Development tools and ecosystem management for the SciTeX package family.

Note

stx.dev delegates to the standalone scitex-dev package. Install with: pip install scitex-dev.

Overview

The dev module provides utilities for maintaining the SciTeX ecosystem of packages, building documentation, managing versions, and creating MCP/CLI wrappers for scientific tools. It is primarily used by package maintainers and contributors rather than end users.

Ecosystem Management

The SciTeX ecosystem comprises 14+ packages. stx.dev provides a unified registry and coordination tools.

import scitex as stx

# List all ecosystem packages
stx.dev.list_versions()

# Check version consistency across the ecosystem
stx.dev.check_versions()

# Synchronize local clones
stx.dev.ecosystem_sync()

# Commit across multiple packages
stx.dev.ecosystem_commit("fix: update shared constants")
Ecosystem Packages

Package

Purpose

scitex

Hub package (re-exports all modules)

scitex-io

File I/O for 30+ formats

scitex-stats

Statistical testing with auto-reporting

scitex-linter

AST-based convention checker

scitex-clew

Claim-evidence-workflow pipeline

figrecipe

Publication-quality plotting

scitex-dev

Development and ecosystem tools

scitex-notification

Multi-backend notifications

scitex-app

Runtime SDK for SciTeX applications

LLM-friendly Types

stx.dev provides structured return types designed for consumption by both humans and AI agents:

from scitex.dev import Result, ErrorCode

# Wrap function results for consistent handling
result = Result(ok=True, data={"accuracy": 0.95})
result = Result(ok=False, error=ErrorCode.NOT_FOUND, message="File missing")

# Decorator to add return_as= parameter
@stx.dev.supports_return_as
def analyze(data):
    return {"mean": data.mean()}

analyze(data, return_as="json")    # JSON string
analyze(data, return_as="dict")    # Python dict

MCP and CLI Wrappers

Convert Python functions into MCP tools or CLI commands:

from scitex.dev import wrap_as_mcp, wrap_as_cli

def my_tool(path: str, threshold: float = 0.5) -> dict:
    """Analyze a data file."""
    ...

# Register as MCP tool (for AI agent access)
mcp_tool = wrap_as_mcp(my_tool)

# Register as CLI command (for terminal access)
cli_cmd = wrap_as_cli(my_tool)

Documentation

Build and search unified documentation across the ecosystem:

# Build Sphinx docs for a package
stx.dev.build_docs("scitex-io")

# Get docstring for any public function
stx.dev.get_docs("stx.io.save")

# Full-text search across all packages
results = stx.dev.search("load_configs")

Hot Reload

Reload modules during interactive development:

stx.dev.reload("scitex.io")    # Re-import scitex.io from disk

HPC Testing

Submit test suites to HPC clusters and poll results:

scitex dev test-hpc --package scitex-io
scitex dev test-hpc-poll --job-id 12345

Bulk Rename

Rename symbols across an entire codebase safely:

stx.dev.bulk_rename(
    root="./src",
    old="old_function_name",
    new="new_function_name",
    dry_run=True,    # Preview changes first
)

API Reference

scitex-dev: Shared developer utilities for the SciTeX ecosystem.

Zero-dependency package providing: - Docs aggregation and serving across all scitex packages - Unified search across APIs, CLI, MCP tools, and documentation - Version management and ecosystem registry - Bulk rename with cross-reference updates - LLM-friendly types (Result, ErrorCode, @supports_return_as)

Public API (20 functions):

# Docs
get_docs, build_docs, search_docs

# Search
search

# Versions
list_versions, check_versions, get_mismatches, fix_mismatches

# LLM-friendly types
Result, ErrorCode, supports_return_as, SideEffect,
classify_exception, handle_result, run_as_cli, run_as_mcp,
result_to_mcp, wrap_as_mcp
scitex.dev.get_docs(package=None, packages=None, format=None, page=None, *, _discover_fn=None, _root_fn=None, _sphinx_fn=None)[source]

Get documentation for one, several, or all installed SciTeX packages.

Resolution chain per package:
  1. Pre-built _sphinx_html/ in installed package → fastest (production)

  2. Sphinx _build/ available → use existing build

  3. Neither → introspect from docstrings + signatures (always works)

Parameters:
  • package (Optional[str]) – Single package name (returns unwrapped result).

  • packages (Optional[list[str]]) – List of package names (returns dict keyed by name).

  • format (Optional[str]) – Output format — None for manifest, “json” for structured, “html” for path to HTML directory.

  • page (Optional[str]) – Specific page name (only with format=”html” or “json”).

Returns:

the doc result directly (unwrapped). - If packages=: dict mapping package name → doc result. - If neither: dict of all discovered packages → doc result.

Return type:

  • If package=

Raises:
  • ValueError – If both package= and packages= are given.

  • LookupError – If a requested package is not found.

scitex.dev.build_docs(package=None, output_dir=None, formats=None, *, _discover_fn=None, _sphinx_fn=None)[source]

Build docs from Sphinx source for one or all packages.

Parameters:
  • package (Optional[str]) – Single package name. None = build all discovered.

  • output_dir (Optional[Path]) – Override output directory. Default: in-place (_build/).

  • formats (Optional[list[str]]) – List of builders (“html”, “json”). Default: [“html”].

Return type:

dict[str, Any]

Returns:

Dict mapping package name → build result (path or error).

Raises:
scitex.dev.search_docs(query, package=None, packages=None, max_results=10, *, _discover_fn=None, _get_one_fn=None)[source]

Search documentation across one, several, or all SciTeX packages.

Simple keyword search over page titles and introspected content. Stdlib only — no external search dependencies.

Parameters:
  • query (str) – Search query string (case-insensitive keyword matching).

  • package (Optional[str]) – Search within a single package.

  • packages (Optional[list[str]]) – Search within specific packages.

  • max_results (int) – Maximum number of results to return.

Returns:

package, name, title, score, match_type. Sorted by relevance score (descending).

Return type:

List of dicts with keys

scitex.dev.search(query, scope='all', package=None, packages=None, max_results=10, fuzzy=True)[source]

Search across the SciTeX ecosystem.

Query syntax:

“save figure” → match any term ‘“exact phrase”’ → match exact phrase “+required term” → term must appear “-excluded” → results with this term are removed

Parameters:
  • query (str) – Search query (supports +required, -excluded, “phrases”).

  • scope (Literal['all', 'api', 'cli', 'mcp', 'docs']) – What to search — “all”, “api”, “cli”, “mcp”, or “docs”.

  • package (Optional[str]) – Limit to a single package.

  • packages (Optional[list[str]]) – Limit to specific packages.

  • max_results (int) – Maximum results to return.

  • fuzzy (bool) – Enable fuzzy matching via difflib (default True).

Returns:

package, name, title, score, scope, match_type. Sorted by relevance (score descending).

Return type:

List of dicts with

scitex.dev.list_versions(packages=None)[source]

List versions for all ecosystem packages.

Parameters:

packages (list[str] | None) – List of package names to check. If None, checks all ecosystem packages.

Returns:

Version information for each package.

Return type:

dict

scitex.dev.check_versions(packages=None)[source]

Check version consistency and return detailed status.

Parameters:

packages (list[str] | None) – List of package names to check. If None, checks all ecosystem packages.

Returns:

Detailed version check results with overall summary.

Return type:

dict

scitex.dev.get_mismatches(packages=None)[source]

Return packages with non-ok status and their issues.

Parameters:

packages (list[str] | None) – Package names to check. None = all ecosystem packages.

Returns:

{package_name: {status, issues, local, git, remote}} for non-ok packages.

Return type:

dict

scitex.dev.fix_mismatches(hosts=None, packages=None, local=True, remote=True, confirm=False, config=None)[source]

Detect version mismatches and fix them.

Combines detect + fix_local + fix_remote. Kept for backward compatibility. Prefer using the individual functions for clarity.

Safety: defaults to preview only. Pass confirm=True to execute.

Return type:

dict[str, Any]

scitex.dev.get_ecosystem_versions(packages=None)[source]

Return a flat {pkg_name: installed_version} dict for the ecosystem.

Thin wrapper over list_versions for consumers that just need the installed-version string (Django health endpoints, Docker HEALTHCHECK scripts, dashboards). Skips all the git / PyPI / pyproject cross- check detail.

Parameters:

packages (list[str] | None) – Package names to check. None = all ecosystem packages.

Returns:

{"scitex": "2.27.3", "figrecipe": "0.28.1", "scitex-io": None}. None means the package is not installed.

Return type:

dict[str, str | None]

Examples

>>> from scitex_dev import get_ecosystem_versions
>>> vers = get_ecosystem_versions()
>>> vers["scitex"]
'2.27.3'
class scitex.dev.Result(success, data=None, error=None, error_code=None, context=<factory>, side_effects=<factory>, hints_on_success=<factory>, hints_on_warning=<factory>, hints_on_error=<factory>, idempotent=False, version=None)[source]

Bases: Generic[T]

Structured result wrapping function output with metadata.

success

Whether the operation succeeded.

Type:

bool

data

The return value on success; None on failure.

Type:

T | None

error

Human-readable error message on failure.

Type:

str | None

error_code

Machine-readable error code (e.g. “E001”).

Type:

str | None

context

Additional context (file paths, parameters, etc.).

Type:

dict

side_effects

Description of mutations performed.

Type:

list[str]

hints_on_success

Related tools the consumer might want (light, “see also” style). Reserved for future use — currently empty.

Type:

list[str]

hints_on_warning

Hints for partial success, deprecation, or approaching limits. Reserved for future use — currently empty.

Type:

list[str]

hints_on_error

Recovery guidance for expected failures (FAQ-style). Accepts None, “”, a single string, or a list.

Type:

list[str]

idempotent

Whether the operation is safe to retry.

Type:

bool

version

API version for response schema evolution.

Type:

str | None

__post_init__()[source]

Normalize hint fields (accept None, “”, single string, or list).

to_dict()[source]

Convert to a plain dictionary, stripping None values.

Return type:

dict

to_json(indent=2)[source]

Serialize to a JSON string.

Return type:

str

property exit_code: int

Map error_code to a CLI exit code.

classmethod json_schema()[source]

Return JSON Schema describing the Result envelope.

Return type:

dict

class scitex.dev.ErrorCode(value)[source]

Bases: str, Enum

Machine-readable error codes for structured error responses.

property exit_code: int

POSIX-style exit code for this error category.

exception scitex.dev.ScitexError(message, *, code=ErrorCode.INTERNAL, remediation=None)[source]

Bases: Exception

Canonical SciTeX exception for structured failures.

Use when the failure crosses an MCP / CLI / HTTP boundary and the caller (often an LLM agent) needs a machine-readable category plus a human remediation hint. For plain Python errors raised inside a script, prefer the standard library exceptions (ValueError, FileNotFoundError, etc.) — see general/03_interface/01_python-api/09_error-handling.md.

Parameters:
  • message (str) – Human-readable description of the failure.

  • code (ErrorCode, optional) – Machine-readable category. Defaults to ErrorCode.INTERNAL.

  • remediation (str, optional) – How to fix it (e.g. "pip install scitex-io[h5]").

Examples

>>> raise ScitexError(
...     "h5py not installed",
...     code=ErrorCode.DEPENDENCY,
...     remediation="pip install scitex-io[h5]",
... )
to_dict()[source]

Serialize to the structured shape consumed by MCP tools and –json.

Return type:

dict[str, str]

scitex.dev.classify_exception(exc)[source]

Classify an exception into an ErrorCode.

Checks for a .error_code attribute first (SciTeXError convention), then falls back to built-in exception type mapping.

Return type:

ErrorCode

scitex.dev.try_import_optional(module_path, attr=None, *, extra=None, pkg=None, package=None)[source]

Import module_path; return None on ImportError.

Parameters:
  • module_path (str) – Module to import. Leading . triggers relative resolution against package (mirrors importlib.import_module()).

  • attr (str, optional) – If given, return getattr(module, attr) instead of the module. A missing attribute is treated identically to a failed import.

  • extra (str, optional) – Name of the pip extra that pulls this dependency (used for the install hint surfaced via last_install_hint()).

  • pkg (str, optional) – Distribution name owning the extra (e.g. "scitex-io").

  • package (str, optional) – Anchor for relative imports.

Returns:

The imported module/attribute, or None on failure.

Return type:

object or None

scitex.dev.last_install_hint(name)[source]

Return the most recently recorded install hint for name (or None).

Return type:

InstallHint | None

class scitex.dev.InstallHint(module, extra, pkg)[source]

Bases: object

Metadata to help a caller surface a useful install message.

scitex.dev.supports_return_as(fn)[source]

Add return_as="result" support to a function.

  • return_as=None (default): data returned, exceptions raised.

  • return_as="result": wrapped in Result.

  • Any other value: passed through to the original function.

Examples

>>> @supports_return_as
... def add(a: int, b: int) -> int:
...     return a + b
>>> add(1, 2)
3
>>> result = add(1, 2, return_as="result")
>>> result.success
True
>>> result.data
3
Return type:

Callable

class scitex.dev.SideEffect(type, target, undoable=False)[source]

Bases: object

A declared side effect of a function.

type

Category (file_create, file_modify, file_delete, network, state_change, cache_write).

Type:

str

target

What is affected (file path, URL, description).

Type:

str

undoable

Whether the effect can be reversed.

Type:

bool

scitex.dev.handle_result(result, as_json=False, file=None)[source]

Format and print a Result, return the exit code.

Parameters:
  • result (Result) – The structured result to display.

  • as_json (bool) – If True, output full JSON. If False, human-friendly text.

  • file (file-like | None) – Output stream. Defaults to stdout/stderr based on success.

Returns:

Exit code suitable for sys.exit().

Return type:

int

scitex.dev.run_as_cli(fn, as_json=False, **kwargs)[source]

Call a @supports_return_as function and exit with proper code.

Parameters:
  • fn (Callable) – A function decorated with @supports_return_as.

  • as_json (bool) – If True, output full JSON.

  • **kwargs (Any) – Arguments to pass to fn.

Return type:

None

scitex.dev.wrap_as_cli(fn, as_json=False, **kwargs)[source]

Call any function and display its result via CLI.

Like wrap_as_mcp but for terminal output. Wraps any plain function in Result, formats based on as_json, and exits with proper exit code.

Parameters:
  • fn (Callable) – Any callable returning data or raising exceptions.

  • as_json (bool) – If True, output full JSON Result. If False, human-friendly text.

  • **kwargs (Any) – Arguments to pass to fn.

Return type:

None

scitex.dev.run_as_mcp(fn, **kwargs)[source]

Call a @supports_return_as function and return MCP-ready JSON.

Parameters:
  • fn (Callable) – A function decorated with @supports_return_as.

  • **kwargs (Any) – Arguments to pass to fn.

Returns:

JSON string with the full Result structure.

Return type:

str

scitex.dev.wrap_as_mcp(fn, *, side_effects=None, hints_on_error=None, idempotent=False, **kwargs)[source]

Call any function and wrap its return in Result JSON.

Unlike run_as_mcp (which requires @supports_return_as), this wraps any plain function. Use this to retrofit existing handlers without modifying the underlying function.

Parameters:
  • fn (Callable) – Any callable returning data or raising exceptions.

  • side_effects (list[str], optional) – Declared side effects (e.g. ["file_create: /tmp/out.csv"]).

  • hints_on_error (list[str], optional) – Recovery guidance for expected failures (FAQ-style).

  • idempotent (bool) – Whether the operation is safe to retry.

  • **kwargs (Any) – Arguments to pass to fn.

Returns:

JSON string with Result structure.

Return type:

str

async scitex.dev.async_wrap_as_mcp(coro_fn, *, side_effects=None, hints_on_error=None, idempotent=False, **kwargs)[source]

Async version of wrap_as_mcp for async handlers.

Parameters:
  • coro_fn (Callable) – Any async callable (coroutine function) returning data or raising exceptions.

  • side_effects (list[str], optional) – Declared side effects.

  • hints_on_error (list[str], optional) – Recovery guidance for expected failures (FAQ-style).

  • idempotent (bool) – Whether the operation is safe to retry.

  • **kwargs (Any) – Arguments to pass to coro_fn.

Returns:

JSON string with Result structure.

Return type:

str

scitex.dev.result_to_mcp(result)[source]

Convert an existing Result to MCP-ready JSON.

Return type:

str

scitex.dev.json_option(fn)[source]

Click decorator: adds --json flag as as_json parameter.

Uses lazy import click so scitex-dev stays stdlib-only.

Return type:

Callable

scitex.dev.dry_run_option(fn)[source]

Click decorator: adds --dry-run flag.

Uses lazy import click so scitex-dev stays stdlib-only.

Return type:

Callable

scitex.dev.add_json_argument(parser)[source]

Add --json flag to an argparse parser.

Return type:

None

scitex.dev.add_dry_run_argument(parser)[source]

Add --dry-run flag to an argparse parser.

Return type:

None

scitex.dev.sync_all(hosts=None, packages=None, stash=True, install=True, safe=True, confirm=False, config=None, *, sync_host_fn=None, enabled_hosts_fn=None)[source]

Sync packages across all enabled hosts.

Safety: defaults to preview only. Pass confirm=True to execute. Parallel: hosts are synced concurrently by default.

Parameters:
  • hosts (list[str] | None) – Host names to sync. None = all enabled hosts.

  • packages (list[str] | None) – Package names. None = host-specific defaults.

  • stash (bool) – Git stash before pull.

  • install (bool) – Pip install after pull.

  • safe (bool) – If True (default), skip packages whose remote working copy is ahead of / diverged from upstream (never clobber unpushed work).

  • confirm (bool) – If False (default), preview only (dry run). If True, execute the sync operation.

  • config (DevConfig | None) – Configuration.

Returns:

{host_name: {package: result}}.

Return type:

dict

scitex.dev.sync_host(host, packages=None, stash=True, install=True, safe=True, confirm=False, config=None, *, host_packages_fn=None)[source]

Sync packages to a remote host via SSH.

Safety: defaults to preview only. Pass confirm=True to execute.

Steps per package: ahead-check (if safe), git stash, git pull, pip install -e ., git stash pop.

Parameters:
  • host (HostConfig) – Target host configuration.

  • packages (list[str] | None) – Package names to sync. None = use host’s configured packages.

  • stash (bool) – Git stash before pull (default True).

  • install (bool) – Pip install after pull (default True).

  • safe (bool) – If True (default), pre-check each remote working copy and skip packages whose HEAD is ahead of / diverged from upstream so we never clobber unpushed commits.

  • confirm (bool) – If False (default), preview only (dry run). If True, execute the sync operation.

  • config (DevConfig | None) – Configuration. Loaded from default if None.

Returns:

Per-package results: {package: {status, commands|output, error}}. status includes ok, skipped_ahead, skipped_diverged, missing, error, timeout, or dry_run.

Return type:

dict

scitex.dev.sync_local(packages=None, confirm=False, config=None, jobs=1, on_progress=None)[source]

Install all local editable packages.

Safety: defaults to preview only. Pass confirm=True to execute.

Parameters:
  • packages (list[str] | None) – Package names. None = all configured packages.

  • confirm (bool) – If False (default), preview only. If True, execute pip install -e.

  • config (DevConfig | None) – Configuration.

  • jobs (int) – Parallel installs. 1 = serial (default). 0 or negative = all CPUs.

  • on_progress (callable | None) – Optional callback f(idx, total, name, status, elapsed) invoked as each package finishes.

Returns:

{package: {status, output|commands}}.

Return type:

dict

scitex.dev.sync_tags(packages=None, confirm=False, config=None)[source]

Push local tags for all packages to origin.

Safety: defaults to preview only. Pass confirm=True to execute.

Parameters:
  • packages (list[str] | None) – Package names. None = all configured packages.

  • confirm (bool) – If False (default), preview only. If True, execute git push –tags.

  • config (DevConfig | None) – Configuration.

Returns:

{package: {status, tag, output|commands}}.

Return type:

dict

scitex.dev.remote_diff(host=None, packages=None, config=None)[source]

Show git diff on remote host(s). Read-only operation.

Parameters:
  • host (str | None) – Host name. None = first enabled host.

  • packages (list[str] | None) – Package names. None = host-configured defaults.

  • config (DevConfig | None) – Configuration.

Returns:

{host_name: {package: {status, files, diff_stat, diff}}}.

Return type:

dict

scitex.dev.remote_commit(host, packages=None, message=None, push=True, confirm=False, config=None)[source]

Commit dirty changes on a remote host and optionally push to origin.

Safety: defaults to preview only. Pass confirm=True to execute.

Parameters:
  • host (str) – Host name (required).

  • packages (list[str] | None) – Package names. None = host-configured defaults.

  • message (str | None) – Commit message. Auto-generated if not provided.

  • push (bool) – Push to origin after commit (default True).

  • confirm (bool) – If False (default), preview only (dry run).

  • config (DevConfig | None) – Configuration.

Returns:

{package: {status, commands|output}}.

Return type:

dict

scitex.dev.pull_local(packages=None, confirm=False, stash=True, config=None)[source]

Pull latest from origin to local repos.

Safety: defaults to preview only. Pass confirm=True to execute.

Parameters:
  • packages (list[str] | None) – Package names. None = all configured packages.

  • confirm (bool) – If False (default), preview only.

  • stash (bool) – If True (default), stash local changes before pull and pop after. If False and repo is dirty, pull proceeds as-is (may fail).

  • config (DevConfig | None) – Configuration.

Returns:

{package: {status, output|commands, stashed}}.

Return type:

dict