# Environment Configuration Schema

**Status**: Implemented and tested (25 test cases)
**File**: `src/env.ts`
**Tests**: `src/tests/env.test.ts`

## Overview

SF uses 80+ `SF_*` environment variables to control behavior at startup and runtime. Previously, these were read directly from `process.env` throughout the codebase, leading to:

- Silent failures when config was missing (no errors, just wrong behavior)
- Type-unsafe access (IDE couldn't auto-complete, linters couldn't check)
- No documentation about what variables exist or what they do
- Scattered default logic (each module computed its own defaults)

This schema provides **centralized, type-safe, validated** access to all SF configuration.

## Quick Start

### Using the env schema

```typescript
import { getCompleteSfEnv } from "./env";

// Get fully validated, type-safe environment config
const config = getCompleteSfEnv();

// IDE completion works:
config.SF_DEBUG;      // boolean
config.SF_HOME;       // string
config.sfHome;        // computed default
config.stateDir;      // computed default (SF_STATE_DIR or SF_HOME)
```

### Setting variables

```bash
# Enable debug mode
export SF_DEBUG=1

# Set custom home directory
export SF_HOME=/opt/sf

# Disable RTK compression
export SF_RTK_DISABLED=1

# Enable the machine surface with prompt tracing
export SF_HEADLESS=1
export SF_HEADLESS_PROMPT_TRACE=1
```

## Schema Categories

### Core Paths (set by loader.ts)

- `SF_PKG_ROOT` — Package installation root (where SF is installed)
- `SF_BIN_PATH` — Path to the SF executable (used for spawning)
- `SF_VERSION` — Package version from package.json
- `SF_WORKFLOW_PATH` — Path to bundled SF-WORKFLOW.md
- `SF_BUNDLED_EXTENSION_PATHS` — Serialized extension manifests
- `SF_CODING_AGENT_DIR` — PI SDK agent directory

### Directories

All directory variables are optional and have sensible defaults:

- `SF_HOME` (default: `~/.sf`) — Root state directory
- `SF_STATE_DIR` (default: `SF_HOME`) — Milestone/slice/task state
- `SF_WORKSPACE_BASE` (default: `SF_STATE_DIR/workspace`) — User workspaces
- `SF_HISTORY_BASE` (default: `SF_STATE_DIR/history`) — Session history
- `SF_NOTIFICATIONS_BASE` (default: `SF_STATE_DIR/notifications`) — Notifications
- `SF_SCHEDULE_FILE` (legacy import only; default: `SF_STATE_DIR/schedule.jsonl`) — pre-DB schedule queue compatibility input
- `SF_RECOVERY_BASE` (default: `SF_STATE_DIR/recovery`) — Recovery artifacts
- `SF_FORENSICS_BASE` (default: `SF_STATE_DIR/forensics`) — Diagnostics
- `SF_SETTINGS_BASE` (default: `SF_STATE_DIR/settings`) — User settings
- And 5+ more for specific recovery/export/cleanup artifacts

### Performance Tuning

- `SF_RTK_DISABLED` (boolean: 0/1, default: 0) — Disable RTK compression
- `SF_RTK_PATH` — Custom path to RTK tool (auto-detected)
- `SF_RTK_REWRITE_TIMEOUT_MS` (integer, default: 5000) — Timeout in ms
- `SF_CIRCUIT_BREAKER_OPEN_DURATION_MS` (integer, default: 60000)
- `SF_CIRCUIT_BREAKER_FAILURE_THRESHOLD` (integer, default: 5)
- `SF_CIRCUIT_BREAKER_HALF_OPEN_MAX_ATTEMPTS` (integer, default: 2)
- `SF_HEADLESS_PROMPT_TRACE_CHARS` (integer, default: 1000)

### Debug Flags

All debug flags are **0 or 1** (disabled or enabled):

- `SF_QUIET` — Suppress startup banner
- `SF_DEBUG` — Enable verbose logging
- `SF_DEBUG_EXTENSIONS` — Enable extension debug logging
- `SF_TRACE_ENABLED` — Collect execution traces
- `SF_HEADLESS` — Suppress TUI for the machine surface, use stdio only
- `SF_HEADLESS_PROMPT_TRACE` — Trace prompts in the machine surface
- `SF_STARTUP_TIMING` — Measure cold-start latency
- `SF_SHOW_TOKEN_COST` — Show LLM token costs
- `SF_FIRST_RUN_BANNER` — Show first-run welcome
- `SF_DISABLE_STARTUP_DOCTOR` — Skip health checks
- `SF_ENGINE_BYPASS` — Use JS implementation instead of Rust
- `SF_DISABLE_NATIVE_SF_PARSER` — Disable native parser
- `SF_DISABLE_NATIVE_SF_GIT` — Disable native git

### Extensions

- `SF_SKILL_MANIFEST_STRICT` (boolean) — Fail on invalid manifests
- `SF_PERMISSION_LEVEL` (enum: `minimal`, `low`, `medium`, `high`, `bypassed`, default: `minimal`)
- `SF_GEMINI_PERMISSION_MODE` (enum: `ask`, `auto`, `deny`, default: `ask`)
- `SF_SESSION_BROWSER_DIR` — Override browser session directory
- `SF_SESSION_BROWSER_CWD` — Override browser working directory
- `SF_FETCH_ALLOWED_URLS` — Comma-separated list of allowed URLs
- `SF_ALLOWED_COMMAND_PREFIXES` — Comma-separated command prefixes

### Recovery and Dispatch

- `SF_RECOVERY_DOCTOR_MODULE` — Custom recovery doctor module
- `SF_RECOVERY_FORENSICS_MODULE` — Custom forensics module
- `SF_RECOVERY_SCOPE` (enum: `unit`, `milestone`, `global`, default: `unit`)
- `SF_RECOVERY_SESSION_FILE` — Recovery session state path
- `SF_RECOVERY_ACTIVITY_DIR` — Recovery activity logs
- `SF_PARALLEL_WORKER` (boolean) — Enable parallel worker mode
- `SF_WORKER_MODEL` — Model for worker dispatch
- `SF_MILESTONE_LOCK` — Lock file for milestone operations
- `SF_SLICE_LOCK` — Lock file for slice operations
- `SF_WORKTREE` — Current git worktree
- `SF_CLI_WORKTREE` — CLI worktree path
- `SF_CLI_WORKTREE_BASE` — CLI worktree base directory
- `SF_CLEANUP_BRANCHES` (boolean, default: 1) — Enable branch cleanup
- `SF_CLEANUP_SNAPSHOTS` (boolean, default: 1) — Enable snapshot cleanup

### Settings Modules

All optional (allow custom implementations):

- `SF_SETTINGS_BUDGET_MODULE` — Custom budget settings
- `SF_SETTINGS_HISTORY_MODULE` — Custom history settings
- `SF_SETTINGS_METRICS_MODULE` — Custom metrics settings
- `SF_SETTINGS_PREFS_MODULE` — Custom preferences settings
- `SF_SETTINGS_ROUTER_MODULE` — Custom router settings
- `SF_WORKSPACE_MODULE` — Custom workspace module
- `SF_SESSION_MANAGER_MODULE` — Custom session manager

### Miscellaneous

- `SF_TRIAGE_SUFFIX` (default: `_triage`) — Suffix for triaged issues
- `SF_PROJECT_ID` — Current project ID (UUID)
- `SF_DOCTOR_SCOPE` (enum: `fast`, `normal`, `deep`, default: `normal`)
- `SF_EXPORT_FORMAT` (enum: `json`, `csv`, `markdown`, default: `json`)
- `SF_TARGET_SESSION_NAME` — Target session for testing
- `SF_TARGET_SESSION_PATH` — Target session path for testing
- `SF_VISUALIZER_BASE` — Visualization output directory

## API Reference

### `getCompleteSfEnv(env?: NodeJS.ProcessEnv): CompleteSfEnv`

**Primary entry point.** Returns fully validated environment configuration with computed defaults.

```typescript
const config = getCompleteSfEnv();

// Type-safe access
console.log(config.SF_DEBUG);        // boolean
console.log(config.SF_HOME);         // string or undefined
console.log(config.sfHome);          // string (computed default)
console.log(config.stateDir);        // string (computed from SF_STATE_DIR || SF_HOME)
console.log(config.agentDir);        // string (computed from SF_AGENT_DIR || SF_CODING_AGENT_DIR || sfHome/agent)
```

### `parseCompleteSfEnv(env?: NodeJS.ProcessEnv): CompleteSfEnv`

**Alternative**: Parse environment with graceful degradation (doesn't throw on validation errors).

### `getSfEnv(env?: NodeJS.ProcessEnv): SfEnv`

**Backward-compatible**: Parses minimal schema (original set of variables). Use `getCompleteSfEnv()` for new code.

### `getEnvValidationSummary(env?: NodeJS.ProcessEnv): { configured: string[], defaults: string[], total: number }`

**For diagnostics**: Shows which variables are explicitly set vs using defaults.

```typescript
const summary = getEnvValidationSummary();
console.log(`Configured: ${summary.configured.length}/${summary.total}`);
console.log(`Using defaults: ${summary.defaults.length}`);
```

## Schema Design

### Zod-based validation

Uses [Zod](https://zod.dev) for composable, type-safe schema definition:

```typescript
// Boolean flags (0 or 1)
const booleanOneZero = z
  .enum(["0", "1"])
  .transform((value) => value === "1")
  .optional();

// Positive integers (parsed from strings)
const positiveInteger = z
  .string()
  .transform((v) => parseInt(v, 10))
  .pipe(z.number().int().positive());

// Enums with defaults
SF_PERMISSION_LEVEL: z.enum(["minimal", "low", "medium", "high", "bypassed"]).optional()
```

### Two-schema approach

**Minimal schema** (`sfEnvSchema`):
- Backward-compatible with existing code
- 8 essential variables
- Used by loader.ts, CLI entry points

**Complete schema** (`completeSfEnvSchema`):
- All 80+ known SF_* variables
- Organized by category
- Comprehensive validation and defaults
- Used by modules needing full environment access

### Graceful degradation

If validation fails:
- `getCompleteSfEnv()` returns partial config (missing fields undefined)
- No throws (never blocks dispatch)
- Warnings logged to stderr if `SF_DEBUG=1`
- Allows SF to run with misconfigured variables (degraded behavior)

## Testing

All 25 tests passing. Coverage includes:

- Boolean flag parsing (0 → false, 1 → true)
- Enum validation (rejects invalid values)
- Integer parsing and validation (positive only)
- Default computation (SF_HOME, SF_STATE_DIR, agentDir)
- Fallback behavior (graceful degradation)
- Round-trip parsing consistency

```bash
# Run tests
npm run test:unit -- src/tests/env.test.ts
```

## Migration Guide

### For existing code reading `process.env.SF_*` directly

**Before**:
```typescript
const debug = process.env.SF_DEBUG === "1";
const home = process.env.SF_HOME || join(homedir(), ".sf");
```

**After**:
```typescript
import { getCompleteSfEnv } from "./env";
const config = getCompleteSfEnv();
const debug = config.SF_DEBUG;  // already parsed boolean
const home = config.sfHome;     // already computed default
```

### For modules needing environment access

1. Import at module level:
   ```typescript
   import { getCompleteSfEnv } from "./env";
   ```

2. Call in initialization (not hot path):
   ```typescript
   const config = getCompleteSfEnv();
   ```

3. Pass config to functions instead of re-reading process.env

## Why This Matters

**Problem**: Silent misconfiguration
```bash
# Typo in env var name (SF_DEBG instead of SF_DEBUG)
export SF_DEBG=1

# SF runs normally but without debug logging (silent failure)
sf run
```

**Solution**: Centralized validation catches mistakes early
```typescript
const config = getCompleteSfEnv();
// Now SF knows all 80+ valid variable names
// Unknown variables can trigger warnings
```

**Benefit**: Type safety
```typescript
// IDE auto-completion works
config.SF_DEBUG              // ✓ recognized
config.SF_DEBG               // ✗ compile error
config.unknownVar            // ✗ compile error

// Future refactors are safe (rename variables with confidence)
```

## Future Enhancements

1. **Config file support** (.sfrc.json with env override)
2. **Env schema generation** (export schema as JSON Schema for docs)
3. **Config diagnostics** (sf doctor --env shows all settings)
4. **Secrets redaction** (API keys not logged)
5. **Per-project overrides** (project-specific .sf/.env)

## See Also

- `src/env.ts` — Implementation
- `src/tests/env.test.ts` — Test suite
- `.nvmrc` — Node.js version (requires Zod support)
