Module: cli_audit.environment
Phase: 2.1 (Foundation)
Purpose: Context-aware environment detection for intelligent installation strategy selection
The environment module detects the execution context (CI/CD, server, or workstation) to inform installation decisions. Different environments require different installation strategies:
- CI/CD: Reproducible, ephemeral, prefer system packages
- Server: Multi-user, system-level, prefer stable versions
- Workstation: Single-user, user-level, prefer latest tools
@dataclass(frozen=True)
class Environment:
"""Detected environment information."""
mode: str
confidence: float
indicators: tuple[str, ...] = ()
override: bool = FalseImmutable dataclass representing detected or configured environment.
| Attribute | Type | Description |
|---|---|---|
mode |
str |
Environment type: "ci", "server", or "workstation" |
confidence |
float |
Detection confidence level (0.0-1.0) |
indicators |
tuple[str, ...] |
Evidence used for detection decision |
override |
bool |
Whether mode was explicitly overridden by user |
__str__() -> str
Returns human-readable representation:
env = Environment(mode="workstation", confidence=0.75, indicators=("display_environment",))
print(env) # "workstation (confidence: 75%)"def detect_environment(
override: str | None = None,
verbose: bool = False
) -> EnvironmentDetect environment type for context-aware installation.
Uses heuristic detection with priority cascade:
- Explicit override (if provided)
- CI/CD indicators (highest confidence: 95%)
- Server indicators (medium confidence: 50-85%)
- Workstation (default fallback: 75%)
| Parameter | Type | Default | Description |
|---|---|---|---|
override |
str | None |
None |
Explicit mode: "ci", "server", "workstation", or None for auto-detect |
verbose |
bool |
False |
Enable verbose logging to stdout |
Environment object with detected or overridden mode
ValueError: Ifoverridevalue is not valid ("ci","server","workstation")
1. CI/CD Detection (Confidence: 95%)
Checks environment variables:
CI,GITHUB_ACTIONS,GITLAB_CI,CIRCLECI,TRAVISJENKINS_HOME,BUILDKITE,DRONE,SEMAPHORE
If any CI variable is set → mode="ci", confidence=0.95
2. Server Detection (Confidence: 50-85%)
Scoring system (threshold: 0.5):
- Multiple active users:
>3 users: +0.3 score>1 users: +0.15 score
- System uptime:
>30 days: +0.25 score>7 days: +0.1 score
- Shared filesystem:
/sharedor/exportexists: +0.2 score
- No display environment:
- No
DISPLAY,WAYLAND_DISPLAY, orSSH_CONNECTION: +0.1 score
- No
If score >= 0.5 → mode="server", confidence=min(score, 0.85)
3. Workstation Detection (Confidence: 75%)
Default fallback when CI/server detection fails.
Indicators checked (informational only):
- Display environment (
DISPLAYorWAYLAND_DISPLAY) - Single user
- Typical workstation paths (
~/Desktop,~/.config)
Always returns: mode="workstation", confidence=0.75
Auto-detection:
from cli_audit import detect_environment
# Detect current environment
env = detect_environment()
print(env.mode) # "workstation"
print(env.confidence) # 0.75
print(env.indicators) # ("display_environment", "single_user", "workstation_paths")Explicit override:
# Force CI mode
env = detect_environment(override="ci")
print(env.mode) # "ci"
print(env.confidence) # 1.0
print(env.override) # TrueVerbose logging:
env = detect_environment(verbose=True)
# Output: "Workstation environment detected (default): ['display_environment', 'single_user']"CI environment detection:
import os
os.environ["GITHUB_ACTIONS"] = "true"
env = detect_environment()
print(env.mode) # "ci"
print(env.confidence) # 0.95
print(env.indicators) # ("env:GITHUB_ACTIONS=true",)Server environment detection:
# On a server with 5 active users and 45 days uptime
env = detect_environment()
print(env.mode) # "server"
print(env.confidence) # 0.55 (0.3 + 0.25)
print(env.indicators) # ("active_users=5", "uptime_days=45")def get_environment_from_config(
config_mode: str | None,
verbose: bool = False
) -> EnvironmentGet environment from configuration file setting.
Wrapper around detect_environment() for config file integration.
| Parameter | Type | Default | Description |
|---|---|---|---|
config_mode |
str | None |
- | Mode from config: "auto", "ci", "server", "workstation", or None |
verbose |
bool |
False |
Enable verbose logging |
Environment object:
"auto"orNone→ triggersdetect_environment(override=None)- Other values → explicit override via
detect_environment(override=config_mode)
Auto-detection from config:
from cli_audit import get_environment_from_config
# Config specifies "auto" or is None
env = get_environment_from_config(config_mode="auto")
# Triggers full auto-detectionExplicit mode from config:
# Config specifies "server"
env = get_environment_from_config(config_mode="server")
print(env.mode) # "server"
print(env.override) # Truefrom cli_audit import detect_environment
env = detect_environment()
if env.mode == "ci":
print("Running in CI/CD - use reproducible installs")
elif env.mode == "server":
print("Running on server - use system packages")
else: # workstation
print("Running on workstation - use user-level tools")from cli_audit import load_config, get_environment_from_config
config = load_config() # Loads .cli-audit.yml
env = get_environment_from_config(config.environment_mode)
print(f"Environment: {env}")
print(f"Confidence: {env.confidence * 100:.0f}%")from cli_audit import detect_environment, select_package_manager
env = detect_environment()
if env.mode == "ci":
# CI: Prefer system packages for reproducibility
pm = select_package_manager("python", env=env)
# Likely returns: pip (system)
elif env.mode == "server":
# Server: Prefer system packages for stability
pm = select_package_manager("python", env=env)
# Likely returns: pip (system) or apt/dnf
else: # workstation
# Workstation: Prefer user-level isolation
pm = select_package_manager("python", env=env)
# Likely returns: uv or pipx (user-level)env = detect_environment()
if env.confidence < 0.6:
print(f"Warning: Low confidence ({env.confidence:.0%}) in detection")
print(f"Indicators: {env.indicators}")
# Prompt user for confirmation or use safe defaults
confirmed = input(f"Detected {env.mode}. Correct? [y/N]: ")
if confirmed.lower() != 'y':
# Use explicit override
mode = input("Enter mode (ci/server/workstation): ")
env = detect_environment(override=mode)from cli_audit import Config, get_environment_from_config
config = Config() # Default or loaded from file
env = get_environment_from_config(config.environment_mode)from cli_audit import detect_environment, select_package_manager
env = detect_environment()
pm = select_package_manager("python", env=env)
# Environment influences PM selection priorityfrom cli_audit import install_tool, detect_environment, Config
config = Config()
env = detect_environment()
result = install_tool(
tool_name="ripgrep",
package_name="ripgrep",
target_version="latest",
config=config,
env=env, # Environment influences installation strategy
language="rust",
)Immutability ensures environment detection results can't be accidentally modified after detection, preventing subtle bugs in multi-threaded contexts.
Different detection methods have different reliability:
- CI indicators (95%): Environment variables are explicit
- Server heuristics (50-85%): Scoring multiple indicators
- Workstation fallback (75%): Safe default, medium confidence
Simplifies decision logic while covering main use cases:
- CI: Ephemeral, reproducible builds
- Server: Multi-user, system-level stability
- Workstation: Single-user, latest tools
More granular modes (developer, production, staging) can be handled via configuration overrides.
Allows users to explicitly set mode when:
- Heuristics fail or produce low confidence
- Running in unusual environments (containers, VMs)
- Testing installation strategies
- Configuration-driven workflows
- Configuration: config.md - Config integration
- Package Managers: package_managers.md - PM selection influenced by environment
- Architecture: ../ARCHITECTURE_DIAGRAM.md - Environment in dependency graph
- ADR-005: ../adr/ADR-005-environment-detection.md - Design rationale
| Version | Date | Changes |
|---|---|---|
| 2.0.0-alpha.6 | 2025-10-09 | Initial Phase 2.1 implementation |
Module Location: cli_audit/environment.py (177 lines)
Exports: Environment, detect_environment, get_environment_from_config
Dependencies: common.py (utility functions)