Schema now accepts the same five levels used elsewhere in the codebase (minimal/low/medium/high/bypassed) instead of the stale full/restricted/ sandbox triple. Docs and env test updated to match. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
322 lines
11 KiB
Markdown
322 lines
11 KiB
Markdown
# 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)
|