Skip to content

Environment Initialization

The init command is the core engine of Protostar. It is not a glorified mkdir script; it is a deterministic state-machine designed to safely aggregate configurations, wire complex development tooling together, and construct robust directory architectures in seconds.

Protostar is designed to be run on Day 1 to build your repository's foundation, but it is perfectly safe to re-run on Day 50 to inject a forgotten dependency or adopt a new static analysis tool.

  • State-Aware Aggregation

    Protostar doesn't just blindly overwrite files. It parses ASTs, deduplicates .gitignore entries, and safely merges configurations. It is perfectly safe to execute on pre-existing codebases.

  • Deterministic Velocity

    Instead of manually copying boilerplate from old repositories or relying on fragile bash scripts, Protostar guarantees a consistent, idempotent environment. It resolves your exact requested state in a fraction of a second, eliminating configuration drift and forgotten dependencies.


Execution Footprints

To understand how Protostar interprets your flags, observe what happens when we execute different domain workflows in an empty directory. Notice how the orchestrator automatically routes tooling configurations, isolates data artifacts, and binds dependencies.

IDE Configuration Footprints

The following repository tree examples assume you have explicitly configured an IDE in your global settings (e.g., ide = "vscode") in addition to globally enabling direnv. This represents the best practice configuration for vscode users scaffolding python environments. If your config remains set to the default None, the .vscode/settings.json file will not be generated, though the universal .vscode/ exclusion will still be safely appended to your .gitignore.

Command: protostar init --python --cli --mypy --pytest --pre-commit --markdownlint

This footprint demonstrates Protostar's ability to wire complex tooling together automatically.

.
├── .gitignore
├── .markdownlint.yaml
├── .pre-commit-config.yaml
├── .python-version
├── pyproject.toml
├── src
├── tests
└── uv.lock
See the generated .gitignore
*~
.DS_Store
.coverage
.env
.idea/
.mypy_cache/
.pytest_cache/
.ruff_cache/
.venv/
.vscode/
Thumbs.db
__pycache__/
coverage.xml
htmlcov/
See the generated .markdownlint.yaml
# Inherit default rules
default: true

# --- Disabled Rules ---

# MD013: Line length
# Rationale: Hard-wrapping text disrupts IDE reading flow, breaks URLs, and creates arbitrary diff churn.
MD013: false

# MD033: Inline HTML
# Rationale: Required for layout elements unsupported by strict Markdown (e.g., <details> blocks, complex tables).
MD033: false

# --- Refined Rules ---

# MD024: Multiple headings with the same content
# Rationale: Allows duplicate subheadings (e.g., "Parameters") under different primary function headings.
MD024:
  siblings_only: true

# --- AST/Parser Enforcement ---

# MD031: Fenced code blocks should be surrounded by blank lines
# Rationale: Prevents strict parsers from rendering backticks as raw text instead of <pre><code> blocks.
MD031: true

# MD032: Lists should be surrounded by blank lines
# Rationale: Prevents contiguous text from merging into lists, ensuring correct AST generation.
MD032: true

# --- Structural Consistency ---

# MD003: Heading style
# Rationale: Enforces ATX style (# Heading) exclusively.
MD003:
  style: "atx"

# MD004: Unordered list style
# Rationale: Enforces dash markers for consistency across the syntax tree.
MD004:
  style: "dash"

# MD009: Trailing spaces
# Rationale: Allows exactly two spaces for hard line breaks; flags arbitrary whitespace.
MD009:
  br_spaces: 2
  strict: false

# MD029: Ordered list item prefix
# Rationale: Enforces the "one" style (1., 1., 1.) to minimize Git diff noise when rearranging list items.
MD029:
  style: "one"
See the generated .pre-commit-config.yaml
repos:
  # 1. Generic hooks (configured to ignore Python to avoid formatting conflicts)
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v6.0.0
    hooks:
      - id: trailing-whitespace
        exclude: \.py$
      - id: end-of-file-fixer
        exclude: \.py$
      - id: check-yaml
      - id: check-added-large-files

  - repo: https://github.com/igorshubovych/markdownlint-cli
    rev: v0.48.0
    hooks:
      - id: markdownlint
        args: ["--fix"]
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.15.11
    hooks:
      - id: ruff-format
      - id: ruff
        args: [ --fix ]
  - repo: https://github.com/pre-commit/mirrors-mypy
    rev: v1.20.1
    hooks:
      - id: mypy
        additional_dependencies:
          - typer
          - rich
          - ruff
          - mypy
          - pytest
          - pytest-cov
          - pytest-mock
          - pre-commit
See the generated pyproject.toml
[project]
name = "demo-project"
version = "0.1.0"
requires-python = ">=3.13"
dependencies = [
    "rich>=15.0.0",
    "typer>=0.24.1",
]

[tool.ruff]
line-length = 88

[tool.ruff.lint]
select = [
    "E",   # pycodestyle errors
    "F",   # pyflakes
    "I",   # isort
    "B",   # flake8-bugbear
    "UP",  # pyupgrade
    "RUF", # ruff-specific rules
]
ignore = []

[tool.mypy]
python_version = "3.13"
strict = true
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true

[tool.pytest.ini_options]
minversion = "7.0"
addopts = "-ra -q --strict-markers"
testpaths = [
    "tests",
]

[dependency-groups]
dev = [
    "mypy>=1.20.1",
    "pre-commit>=4.5.1",
    "pytest>=9.0.3",
    "pytest-cov>=7.1.0",
    "pytest-mock>=3.15.1",
    "ruff>=0.15.11",
]

The Intelligence:

  • Dependency Locking: Protostar instantly locked typer and rich from the CLI preset.
  • AST Configuration: It didn't just dump strings into pyproject.toml. It constructed the TOML Abstract Syntax Tree (AST), gracefully configuring [tool.ruff], [tool.mypy], and [tool.pytest.ini_options] alongside the dev-dependencies.
  • Dynamic Hooks: In .pre-commit-config.yaml, Protostar didn't just add a generic mypy hook. It dynamically evaluated your environment footprint and injected your CLI dependencies directly into Mypy's additional_dependencies block. This guarantees your CI pipeline won't fail due to missing stubs.
  • A Note on Speed: Standard Protostar executions take fractions of a second. However, because --pre-commit was flagged, Protostar queued a pre-commit autoupdate subprocess at the end of the run to ensure your git hooks are pinned to the absolute latest network releases. This shifts the total execution time to roughly ~4-9 seconds.

Command: protostar init --python --astro

This footprint focuses on managing heavy, serialized data assets and preventing repository bloat.

.
├── .gitattributes
├── .gitignore
├── .python-version
├── data
│   ├── catalogs
│   └── fits
├── notebooks
├── pyproject.toml
├── src
└── uv.lock
See the generated .gitattributes
# Astrophysics binary safety
*.fits binary
*.fit  binary
*.fts  binary

# Improve Jupyter Notebook diffs
*.ipynb text eol=lf

*.ipynb diff=jupyternotebook

*.ipynb merge=jupyternotebook
See the generated .gitignore
*.csv
*.fit
*.fits
*.fts
*.parquet
*~
.DS_Store
.env
.idea/
.ipynb_checkpoints/
.ruff_cache/
.venv/
.vscode/
Thumbs.db
__pycache__/
See the generated pyproject.toml
[project]
name = "demo-project"
version = "0.1.0"
requires-python = ">=3.13"
dependencies = [
    "astropy>=7.2.0",
    "astroquery>=0.4.11",
    "matplotlib>=3.10.8",
    "nbdime>=4.0.4",
    "numpy>=2.4.4",
    "pandas>=3.0.2",
    "photutils>=3.0.0",
    "scipy>=1.17.1",
    "specutils>=2.3.0",
]

[tool.ruff]
line-length = 88

[tool.ruff.lint]
select = [
    "E",   # pycodestyle errors
    "F",   # pyflakes
    "I",   # isort
    "B",   # flake8-bugbear
    "UP",  # pyupgrade
    "RUF", # ruff-specific rules
]
ignore = []

[dependency-groups]
dev = [
    "ruff>=0.15.11",
]

The Intelligence:

  • Directory Scaffolding: It dynamically injected data/catalogs and data/fits, automatically isolating your telemetry and catalogs from the source code.
  • Binary Safety: It generated a .gitattributes file explicitly marking *.fits files as binary, and configuring *.ipynb for better text diffing.
  • Notebook Diffing: It automatically configured nbdime at the git level, saving you from parsing unreadable JSON diffs when tracking Jupyter Notebooks.
  • Artifact Exclusions: The .gitignore was populated with *.fits, *.csv, and *.parquet, preventing you from accidentally committing massive telemetry cubes to version control.

Command: protostar init --python --ml --docker

This footprint focuses on containerization and strictly excluding model artifacts.

.
├── .dockerignore
├── .gitignore
├── .python-version
├── data
├── models
├── notebooks
├── pyproject.toml
├── src
└── uv.lock
See the generated .dockerignore
*.log
*.onnx
*.pt
*.pth
*.safetensors
*~
.DS_Store
.env
.git/
.idea/
.ipynb_checkpoints/
.python-version
.ruff_cache/
.venv/
.vscode/
README*
Thumbs.db
__pycache__/
docs/
mlruns/
runs/
tests/
wandb/
See the generated .gitignore
*.log
*.onnx
*.pt
*.pth
*.safetensors
*~
.DS_Store
.env
.idea/
.ipynb_checkpoints/
.ruff_cache/
.venv/
.vscode/
Thumbs.db
__pycache__/
mlruns/
runs/
wandb/
See the generated pyproject.toml
[project]
name = "demo-project"
version = "0.1.0"
requires-python = ">=3.13"
dependencies = [
    "huggingface-hub>=1.11.0",
    "scikit-learn>=1.8.0",
    "torch>=2.11.0",
    "tqdm>=4.67.3",
]

[tool.ruff]
line-length = 88

[tool.ruff.lint]
select = [
    "E",   # pycodestyle errors
    "F",   # pyflakes
    "I",   # isort
    "B",   # flake8-bugbear
    "UP",  # pyupgrade
    "RUF", # ruff-specific rules
]
ignore = []

[dependency-groups]
dev = [
    "ruff>=0.15.11",
]

The Intelligence:

  • Context Generation: Because --docker was flagged, Protostar read the computed VCS ignores from the ML environment and generated a highly optimized .dockerignore. It strips out .venv, .git, local caches, and test artifacts to keep your container build context incredibly lightweight.
  • Model Checkpoints: The ML preset aggressively injects ignores for tensor artifacts (*.pth, *.pt, *.onnx, *.safetensors) and experiment tracking directories (wandb/, mlruns/) to ensure massive model weights never pollute the git tree.

The Python Gravity Well

Protostar is engineered specifically to accelerate Python development pipelines. Its Python scaffolding (specifically leveraging uv) is highly refined, deeply integrated, and serves as the exclusive focus of the engine.


Progressive Scaffolding & Collisions

Developers are rightfully terrified of CLI tools that touch their existing configurations. Protostar is engineered specifically to alleviate this anxiety.

Lets say you initialized a machine learning repo yesterday with protostar init --python --ml --docker

But today you remembered you'll be doing quasar analysis, and you want to enforce strict typing with mypy

You simply run protostar init --python --astro --mypy --docker in that existing directory.

Because Protostar detects existing configuration markers (like pyproject.toml), it instantly halts the execution and triggers the Gravitational Anomaly intercept prompt:

Protostar Ignition Sequence Initiated

Gravitational Anomaly: Protostar detected existing configuration files in the workspace.
  - pyproject.toml

? How would you like to proceed?
  » Merge      (Safely injects missing configs; preserves existing user data)
    Overwrite  (Forces injection; updates existing keys to match Protostar)
    Abort      (Safely exit without modifying the environment)

If you select Merge, Protostar performs a surgical AST injection.

  • It leaves your existing torch and huggingface-hub dependencies completely untouched.
  • It alphabetically merges in astropy, photutils, and specutils.
  • It seamlessly drops the [tool.mypy] configuration block into the TOML file.
  • It appends *.fits and .mypy_cache/ to your existing .gitignore.
See the comparison
.
├── .dockerignore
├── .gitignore
├── .python-version
├── data
├── models
├── notebooks
├── pyproject.toml
├── src
└── uv.lock
.
├── .dockerignore
├── .gitattributes
├── .gitignore
├── .python-version
├── data
│   ├── catalogs
│   └── fits
├── models
├── notebooks
├── pyproject.toml
├── src
└── uv.lock

*.log
*.onnx
*.pt
*.pth
*.safetensors
*~
.DS_Store
.env
.git/
.idea/
.ipynb_checkpoints/
.python-version
.ruff_cache/
.venv/
.vscode/
README*
Thumbs.db
__pycache__/
docs/
mlruns/
runs/
tests/
wandb/
*.log
*.onnx
*.pt
*.pth
*.safetensors
*~
.DS_Store
.env
.git/
.idea/
.ipynb_checkpoints/
.python-version
.ruff_cache/
.venv/
.vscode/
README*
Thumbs.db
__pycache__/
docs/
mlruns/
runs/
tests/
wandb/
*.csv
*.fit
*.fits
*.fts
*.parquet
.mypy_cache/

*.log
*.onnx
*.pt
*.pth
*.safetensors
*~
.DS_Store
.env
.idea/
.ipynb_checkpoints/
.ruff_cache/
.venv/
.vscode/
Thumbs.db
__pycache__/
mlruns/
runs/
wandb/
*.log
*.onnx
*.pt
*.pth
*.safetensors
*~
.DS_Store
.env
.idea/
.ipynb_checkpoints/
.ruff_cache/
.venv/
.vscode/
Thumbs.db
__pycache__/
mlruns/
runs/
wandb/
*.csv
*.fit
*.fits
*.fts
*.parquet
.mypy_cache/

[project]
name = "demo-project"
version = "0.1.0"
requires-python = ">=3.13"
dependencies = [
    "huggingface-hub>=1.11.0",
    "scikit-learn>=1.8.0",
    "torch>=2.11.0",
    "tqdm>=4.67.3",
]

[tool.ruff]
line-length = 88

[tool.ruff.lint]
select = [
    "E",   # pycodestyle errors
    "F",   # pyflakes
    "I",   # isort
    "B",   # flake8-bugbear
    "UP",  # pyupgrade
    "RUF", # ruff-specific rules
]
ignore = []

[dependency-groups]
dev = [
    "ruff>=0.15.11",
]
[project]
name = "demo-project"
version = "0.1.0"
requires-python = ">=3.13"
dependencies = [
    "astropy>=7.2.0",
    "astroquery>=0.4.11",
    "huggingface-hub>=1.11.0",
    "matplotlib>=3.10.8",
    "nbdime>=4.0.4",
    "numpy>=2.4.4",
    "pandas>=3.0.2",
    "photutils>=3.0.0",
    "scikit-learn>=1.8.0",
    "scipy>=1.17.1",
    "specutils>=2.3.0",
    "torch>=2.11.0",
    "tqdm>=4.67.3",
]

[tool.ruff]
line-length = 88

[tool.ruff.lint]
select = [
    "E",   # pycodestyle errors
    "F",   # pyflakes
    "I",   # isort
    "B",   # flake8-bugbear
    "UP",  # pyupgrade
    "RUF", # ruff-specific rules
]
ignore = []

[tool.mypy]
python_version = "3.13"
strict = true
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true

[dependency-groups]
dev = [
    "mypy>=1.20.1",
    "ruff>=0.15.11",
]

Curious how Protostar safely merges a pyproject.toml without breaking existing keys or stripping your comments? Read the Mechanics: Executor deep dive.

Strict Footprint Validation

Protostar enforces explicit dependency boundaries for all tooling operations. If you pass an impossible or conflicting configuration matrix via the CLI, Protostar will not crash or dump inert configurations into your repository. It evaluates the topological constraints, drops the invalid tool, and prints a clean diagnostic warning before proceeding with the rest of the valid scaffolding sequence.

The Capabilities Matrix

You can mix and match these flags to generate exactly the environment you need. To view this matrix in your terminal at any time, run protostar help init.

Protostar Help Init