API Reference
Top-level modules that survived the 2026-05 restructure. Removed
scitex_dev.{skills,fix,ci,deploy,versions,docs,search,_discovery,_manifest,_builder}
were absorbed into _cli/, _creds/, _ecosystem/, etc. — see
the CLI surface and per-subcommand entry points for the live API.
CLI entry point
Backward-compat shim — re-exports from scitex_dev._dispatch.
The reusable docs/skills CLI mixins moved to scitex_dev._dispatch in 0.11.0. This shim keeps from scitex_dev.cli import … working for downstream packages (figrecipe, scitex-io, scitex-app, etc.) until they migrate. Will be removed in 0.12.0.
Also the public promotion target for attach_shell_completion (previously only reachable via the private scitex_dev._cli._completion path — see docs/adr/0003-ecosystem-boundary-ports-and-producers.md). Resolved lazily via __getattr__ (PEP 562) rather than a top-level import: eagerly importing scitex_dev._cli triggers _cli/_root.py’s module-level registration of the ENTIRE subcommand tree (several hundred ms) as a side effect, which every other symbol in this lightweight shim module deliberately avoids paying.
- scitex_dev.cli.docs_click_group(package: str, name: str = 'docs')[source]
Create a Click command group for docs (requires Click installed).
- Usage::
from scitex_dev.cli import docs_click_group cli.add_command(docs_click_group(package=”scitex-writer”))
- scitex_dev.cli.register_docs_subcommand(subparsers: _SubParsersAction, package: str) ArgumentParser[source]
Register
docswithlistandgetverb subcommands.- Usage::
scitex-stats docs list # List doc pages scitex-stats docs get # Show available pages scitex-stats docs get api # Show specific page
- scitex_dev.cli.register_skills_subcommand(subparsers: _SubParsersAction, package: str) ArgumentParser[source]
Register
skillswithlistandgetverb subcommands.- Usage::
scitex-stats skills list # List skill pages scitex-stats skills get # Show main SKILL.md scitex-stats skills get test-selection # Show a reference page
CLI subcommand groups
SciTeX-dev CLI package — entry point + reusable subcommand mixins.
Module map: - _root — top-level Click group main (the scitex-dev console script entry point) - _utils — handle_result, run_as_cli, wrap_as_cli, json/dry-run option helpers - _completion — shell tab-completion installer - _doctor — scitex-dev doctor - _stats — scitex-dev ecosystem stats - audit/ — _summary (was _cli_audit), _api, _project, _skills - ecosystem/ — _registry (was _cli_ecosystem) - quality/ — _check (was _cli_quality), _frontmatter - skills/ — _manage (was _cli_skills), _tags
The main callable is the [project.scripts] target — scitex_dev._cli:main must remain importable.
- scitex_dev._cli.add_dry_run_argument(parser: Any) None[source]
Add
--dry-runflag to an argparse parser.
- scitex_dev._cli.dry_run_option(fn: Callable) Callable[source]
Click decorator: adds
--dry-runflag.Uses lazy
import clickso scitex-dev stays stdlib-only.
- scitex_dev._cli.handle_result(result: Result, as_json: bool = False, file: Any = None) int[source]
Format and print a Result, return the exit code.
- scitex_dev._cli.json_option(fn: Callable) Callable[source]
Click decorator: adds
--jsonflag asas_jsonparameter.Uses lazy
import clickso scitex-dev stays stdlib-only.
- scitex_dev._cli.run_as_cli(fn: Callable, as_json: bool = False, **kwargs: Any) None[source]
Call a
@supports_return_asfunction and exit with proper code.- Parameters:
fn (Callable) – A function decorated with
@supports_return_as.as_json (bool) – If True, output full JSON.
**kwargs – Arguments to pass to
fn.
- scitex_dev._cli.wrap_as_cli(fn: Callable, as_json: bool = False, **kwargs: Any) None[source]
Call any function and display its result via CLI.
Like
wrap_as_mcpbut for terminal output. Wraps any plain function in Result, formats based onas_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 – Arguments to pass to
fn.
Core
Core glue: config, types, errors, dispatch, discovery, manifests.
Ecosystem
SciTeX ecosystem registry, dependency graph, and package-state utilities.
Module map: - _core — ECOSYSTEM registry + get_all_packages / get_local_path - _graph — pyproject dependency graph (discover, parse, mermaid/dot, cycles) - _packages — multi-host package SHA collection + audit/sync helpers
- scitex_dev._ecosystem.get_all_packages() List[str][source]
Get list of all ecosystem package names.
- scitex_dev._ecosystem.get_local_path(package: str) Path | None[source]
Get expanded local path for a package.
- scitex_dev._ecosystem.is_mcp_mountable(package: str) bool[source]
Return
Trueiffpackagemay be auto-mounted onto the umbrella MCP.A package is NOT mountable when any of the following hold:
it is unknown to the registry (fail closed — nothing to mount),
its
ECOSYSTEMrecord isarchived,its
categoryis one the aggregator never mounts (umbrella/template),its record sets
"mcp_mountable": False,it is listed in
_MCP_UNMOUNTABLE.
This is the SSoT the umbrella aggregator consults so the skip policy lives in one place (scitex-dev), not duplicated in scitex-python.
- scitex_dev._ecosystem.mountable_peers() List[str][source]
Return the ordered list of packages the umbrella MCP should mount.
- scitex_dev._ecosystem.should_skip_audit(package: str, auditor: str) tuple[bool, str][source]
Return
(skip, reason)for runningauditoronpackage.auditoris one of the keys in_CATEGORY_SKIP. Unknown auditors return(False, "")— fail open so a typo doesn’t silently skip.Skip semantics:
archived packages are skipped for every auditor.
per-auditor categories listed in
_CATEGORY_SKIPare skipped.unknown package (not in ECOSYSTEM) is NOT skipped — the auditor’s own not-found path will handle it.
Credentials
Ecosystem-wide Claude Code credential rotation.
Multiplexes ~/.claude/.credentials.json to the GitHub Actions secret
slot CLAUDE_CODE_CREDENTIALS_JSON (with sha256 sidecar variable
CLAUDE_CODE_CREDENTIALS_JSON_SHA256) across every package in the
SciTeX ecosystem registry.
Distinct from the single-repo sac dev upload-credentials-to-github
command — that one writes the package-prefixed SAC_… slot. This
module uses the canonical un-prefixed name shared by every scitex
package that consumes Claude Code OAuth.
- class scitex_dev._creds.RotateResult(package: str, repo: str | None, status: str, message: str = '')[source]
Per-repo rotation outcome.
- scitex_dev._creds.rotate_all(*, packages: Iterable[str] | None = None, only: Iterable[str] | None = None, exclude: Iterable[str] | None = None, source_path: Path = PosixPath('/home/docs/.claude/.credentials.json'), dry_run: bool = False, force: bool = False, ecosystem: dict | None = None, local_path_lookup=None) list[RotateResult][source]
Rotate the credential across every (or a filtered set of) repo.
Returns one
RotateResultper package processed. Returns an empty list if the source is silently absent/expired — that is the “fresh laptop / expired token” exit-0 path, deliberately quiet.
- scitex_dev._creds.validate_source(path: Path = PosixPath('/home/docs/.claude/.credentials.json'), *, now_ms: Callable[[], int]=<function _now_ms>) SourceState | None[source]
Return SourceState, or None if rotation should be a silent no-op.
Silent-no-op (returns None) is reserved for the operator-on-a-fresh- -laptop / expired-token cases that should NOT spam every hour:
source file is missing
claudeAiOauth.expiresAt(orexpires_at) is in the past
Raises
ValueErrorfor cases that DO deserve a loud error:file present but doesn’t parse as JSON
file parses but has no
claudeAiOauthkey
Docs (search / index helpers)
Docs build, search, and skills aggregation utilities.
Branding
Ecosystem branding / translation registry.
Single source of truth for SciTeX-ecosystem brand identities (pypi name,
import name, umbrella attr, alias, display, method-prefix, env-prefix).
See registry.yaml for entries and BRANDING_TRANSLATION_REGISTRY_PLAN.md
for design rationale.
Runtime API (minimal — only what figrecipe + socialia need):
get(brand_key, field) -> str
translate(name, *, from_brand, to_brand) -> str
register_method_aliases(cls, *, brand_key) -> None
get_env(key, *, brand_key, default=None) -> str | None
Plus low-level helpers used by the auditor:
get_brand(brand_key) -> Brand
iter_brands() -> Iterator[tuple[str, Brand]]
- scitex_dev._branding.get(brand_key: str, field: str) Any[source]
Look up a single field. No fallbacks — registry is authoritative.
- scitex_dev._branding.get_brand(brand_key: str) Brand[source]
Whole entry for one brand. Raises KeyError if unknown.
- scitex_dev._branding.get_env(key: str, *, brand_key: str, default: str | None = None) str | None[source]
Read an environment variable using the brand’s declared env_prefix, with a fallback to the counterpart brand’s prefix (for white-label rebrands), then the unprefixed name, then default.
- Lookup order:
<this brand env_prefix>_<key><counterpart env_prefix>_<key>(if a counterpart exists)<key>(unprefixed)default
- scitex_dev._branding.iter_brands() Iterator[tuple[str, Brand]][source]
Iterate (brand_key, entry). For audit rules.
- scitex_dev._branding.register_method_aliases(cls, *, brand_key: str) None[source]
Walk
dir(cls)and bind the counterpart-brand’s prefix as aliases.For each method starting with
<this brand>.method_prefix, bind an attribute named with the counterpart’s method_prefix that points at the same function. The alias gets__module__rebound to the counterpart’s import path sohelp()on the umbrella side doesn’t leak the native package name.No-op if the brand has no counterpart or no method_prefix.
- scitex_dev._branding.translate(name: str, *, from_brand: str, to_brand: str) str[source]
Layer-5 only — swap one brand’s method_prefix for the counterpart’s.
Raises ValueError if from_brand/to_brand are not declared as native/umbrella counterparts in the registry.
>>> translate("fr_conf_mat", from_brand="figrecipe", to_brand="scitex-plt") 'stx_conf_mat'
Release
Release-related utilities (CI, deploy, version bumpers, lint gates, RTD onboarding).
Testing helpers
Pytest helpers for downstream SciTeX packages.
Public re-exports: - audit_all_for_package — assert audit-all <pkg> exits 0.
Each ecosystem package drops a one-liner test that calls this helper so the package’s local pytest run includes the same audit gates that CI used to run as a separate workflow. See the auto-generated tests/test_audit.py template (via scitex-dev ecosystem write-audit-test <pkg>) and the skill leaf _skills/general/02_package/07_github-actions.md for the canonical shape.
- scitex_dev.testing.audit_all_for_package(distribution: str, *, path: str | Path | None = None, timeout: float = 550.0, skip_rules: tuple[str, ...] = ()) None[source]
Run scitex-dev ecosystem audit-all <distribution> and assert exit 0.
- Parameters:
distribution – ECOSYSTEM key (e.g.
"scitex-io","scitex-stats","socialia").path –
The checkout to audit — pass it, and pass the tree the test itself lives in. PASS THIS. Without it, audit-all has no argument telling it WHICH tree you meant, so it falls back to resolving the distribution by import location / a
~/proj/<name>development guess. On a CI runner that guess is somebody else’s tree — or the wrong commit of your own — and the gate then grades source that is NOT the commit under test while reporting a confident pass/fail. A gate that grades the wrong tree is worse than no gate.The idiomatic call from a package’s
tests/test_audit.pyis therefore to anchor on the test file itself, which is by construction inside the checkout pytest is running against:from pathlib import Path audit_all_for_package( "scitex-io", path=Path(__file__).resolve().parents[1] )
Threaded through as
--path <path>to theaudit-allCLI, which forwards it to every sub-auditor that accepts it (audit-project, audit-django, audit-python-apis — the source-tree-reading ones). The distribution NAME is still required and is still used for skill/rule lookup;pathonly decides which source gets read.None(the default) preserves the historical resolve-by-guess behaviour for callers that haven’t migrated. It is a compatibility shim, not a recommendation.timeout – Per-test wall-clock cap. audit-all fans out to SIX sub-auditors (audit-cli, audit-mcp-tools, audit-skills, audit-python-apis, audit-project, audit-django), each a fresh Python subprocess that re-imports the target package — real measured wall-clock on a loaded/NFS-backed host runs well past the previous 120s default even when the audit itself is completely clean (verified directly: scitex-dev ecosystem audit-all scitex-dev –path <worktree> exits 0, and a bare untimed run completed clean end-to-end in ~455s under load). 120s was tuned for “a slow PyPI install,” not for the six-subprocess audit sweep itself. 550s gives real headroom while staying under the
test_audit_all_cleancall site’s@pytest.mark.timeout(600)override (seetests/develop/test_audit.py/ theecosystem install-audit-gatetemplate that generates it for every other package) so THIS subprocess timeout fires first with a readable audit-output error message, instead of pytest-timeout’s generic SIGALRM traceback winning the race. A genuinely wedged auditor still times out, just later.Bypass
------
the (audit (the test calls pytest.skip instead of running)
the
pre-existing (subprocess). Use during a remediation push when)
runs (violations would block unrelated test)
developing (or when)
for (locally without scitex-dev's audit corpus available. CI)
silent. (release branches MUST NOT set this — drift goes)
- Raises:
AssertionError – If the subprocess returns non-zero. The full stdout + stderr are included in the message so the failing rule is visible in the test report without re-running the audit by hand.
Linter
SciTeX Linter — enforce reproducible research patterns via AST analysis.
MCP server
scitex-dev’s OWN FastMCP server.
The MCP helper utilities (wrap_as_mcp, register_docs_tools, get_tools_sync, …) used to live here but are now ecosystem-glue under scitex_dev._ecosystem._mcp and re-exported as the public scitex_dev.ecosystem surface. This package retains only the _server module that hosts scitex-dev’s own MCP tools.
Dashboard
Flask dashboard for scitex version management.
- scitex_dev.dashboard.create_app() Flask[source]
Create and configure the Flask application.
- Returns:
Configured Flask application.
- Return type:
Flask