﻿# Environment Configuration Guide

Guide for collecting complete environment information and generating `harness/config/environment.json` during harness creation.

## Overview

`environment.json` is the contract between ecl-harness-engineer and harness-executor. It describes everything the executor needs to know to start the application, set up dependencies, and run verification — but it does NOT define what to verify. Verification configuration (`verify.json`) is dynamically generated by harness-executor at task runtime.

> **Key principle**: ecl-harness-engineer answers "what does this project need to run?" — harness-executor answers "what should we check after making changes?"

---

## environment.json Schema (v2.0)

```json
{
  "version": "2.0",
  "project": {
    "name": "my-project",
    "type": "web-api | cli | frontend | library | hybrid",
    "language": "go | typescript | python | java | rust",
    "description": "Brief project description"
  },
  "runtime": {
    "language": "go",
    "version": "1.22",
    "package_manager": "go | npm | pnpm | yarn | pip | poetry | uv | maven | gradle",
    "build_command": "go build ./...",
    "test_command": "go test ./...",
    "lint_command": "make lint-arch"
  },
  "startup": {
    "command": "go run ./cmd/server",
    "args": ["--port", "${PORT:-8080}"],
    "working_dir": ".",
    "env": {
      "PORT": "${PORT:-8080}",
      "ENV": "development",
      "LOG_LEVEL": "${LOG_LEVEL:-debug}"
    },
    "readiness": {
      "type": "http",
      "config": {
        "endpoint": "/health",
        "port": "${PORT:-8080}",
        "expected_status": 200,
        "timeout_seconds": 30,
        "poll_interval_ms": 500
      }
    }
  },
  "services": [
    {
      "name": "postgres",
      "type": "database",
      "required": true,
      "image": "postgres:15",
      "ports": ["127.0.0.1:5432:5432"],
      "env": {
        "POSTGRES_USER": "${DB_USER:-postgres}",
        "POSTGRES_PASSWORD": "${DB_PASSWORD}",
        "POSTGRES_DB": "${DB_NAME:-app}"
      },
      "health_check": "pg_isready -U ${DB_USER:-postgres}",
      "connection": {
        "url_env": "DATABASE_URL",
        "default_url": "postgres://${DB_USER:-postgres}:${DB_PASSWORD}@localhost:5432/${DB_NAME:-app}?sslmode=disable"
      },
      "setup": {
        "migration_command": "go run ./cmd/migrate up",
        "seed_command": "go run ./cmd/seed"
      }
    }
  ],
  "env_vars": {
    "required": {
      "DATABASE_URL": {
        "purpose": "PostgreSQL connection string",
        "sensitive": true,
        "example": "postgres://user:pass@localhost:5432/dbname"
      },
      "JWT_SECRET": {
        "purpose": "JWT token signing key",
        "sensitive": true,
        "test_value_ok": true,
        "test_value": "test-secret-do-not-use-in-production"
      }
    },
    "optional": {
      "PORT": {
        "purpose": "HTTP server port",
        "default": "8080",
        "sensitive": false
      },
      "LOG_LEVEL": {
        "purpose": "Logging verbosity",
        "default": "info",
        "sensitive": false
      }
    }
  },
  "endpoints": {
    "health": "/health",
    "base_url": "http://localhost:${PORT:-8080}"
  },
  "scripts": {
    "setup": "harness/scripts/setup-env.sh",
    "start": "harness/scripts/start-server.sh",
    "teardown": "harness/scripts/teardown-env.sh"
  },
  "_meta": {
    "generated_by": "ecl-harness-engineer",
    "generated_at": "2026-03-30T10:00:00Z",
    "schema_version": "2.0",
    "requires_user_input": ["DATABASE_URL", "JWT_SECRET"],
    "todos": [
      "Confirm Redis connection if caching is needed"
    ]
  }
}
```

---

## Detection Strategy (4-Step Pipeline)

### Step 1: Detect Project Type and Language

```bash
# Language detection (high confidence)
test -f go.mod && echo "go"
test -f package.json && echo "typescript/javascript"
test -f pyproject.toml && echo "python"
test -f requirements.txt && echo "python"
test -f Cargo.toml && echo "rust"
test -f pom.xml && echo "java-maven"
test -f build.gradle && echo "java-gradle"

# Project type detection (medium confidence)
# Server indicators
grep -rq "http.ListenAndServe\|gin.Default\|chi.NewRouter\|echo.New" --include="*.go" . && echo "web-api"
grep -q '"express"\|"fastify"\|"koa"\|"hono"\|"nest"' package.json 2>/dev/null && echo "web-api"
grep -rq "FastAPI\|Flask\|Django" --include="*.py" . && echo "web-api"

# CLI indicators
test -d cmd/cli && echo "cli"
grep -rq "cobra\|urfave/cli" --include="*.go" . && echo "cli"
grep -q '"commander"\|"yargs"\|"oclif"' package.json 2>/dev/null && echo "cli"

# Frontend indicators
grep -q '"react"\|"vue"\|"svelte"\|"next"\|"nuxt"' package.json 2>/dev/null && echo "frontend"

# Library indicators (no entry point, exports only)
grep -q '"main"\|"bin"' package.json 2>/dev/null || echo "library"
```

### Step 2: Detect Startup Command

**Priority order** — use the first successful detection:

| Priority | Source | Command |
|----------|--------|---------|
| 1 | Existing `harness/config/environment.json` | `jq .startup.command environment.json` |
| 2 | Dockerfile CMD/ENTRYPOINT | `grep -E "^(CMD|ENTRYPOINT)" Dockerfile` |
| 3 | docker-compose.yml command | `grep "command:" docker-compose.yml` |
| 4 | Makefile targets | `grep -E "^(run|start|serve|dev):" Makefile` |
| 5 | package.json scripts | `jq '.scripts.start // .scripts.dev' package.json` |
| 6 | Go cmd/ directory | `ls cmd/*/main.go` → `go run ./cmd/<name>` |
| 7 | Python main module | `test -f main.py && echo "python main.py"` |
| 8 | **User confirmation tool** | Required if all auto-detection fails; in Codex use `request_user_input` |

```bash
# Go: Detect startup command
if test -d cmd/; then
    SERVER_CMD=""
    # Look for server-like directories
    for dir in cmd/*/; do
        name=$(basename "$dir")
        if echo "$name" | grep -qiE "server|api|web|app|service"; then
            SERVER_CMD="go run ./$dir"
            break
        fi
    done
    # If no server-like dir, check if only one cmd/ exists
    if [ -z "$SERVER_CMD" ]; then
        cmd_count=$(ls -d cmd/*/ 2>/dev/null | wc -l)
        if [ "$cmd_count" -eq 1 ]; then
            SERVER_CMD="go run ./$(ls -d cmd/*/)"
        fi
    fi
fi

# Node.js: Check package.json
if test -f package.json; then
    DEV_CMD=$(jq -r '.scripts.dev // empty' package.json 2>/dev/null)
    START_CMD=$(jq -r '.scripts.start // empty' package.json 2>/dev/null)
    # Detect package manager
    test -f pnpm-lock.yaml && PKG_MGR="pnpm"
    test -f yarn.lock && PKG_MGR="yarn"
    test -f bun.lockb && PKG_MGR="bun"
    PKG_MGR="${PKG_MGR:-npm}"
fi

# Python: Check for framework
if grep -q "FastAPI\|Flask" requirements.txt pyproject.toml 2>/dev/null; then
    # Look for uvicorn/gunicorn patterns
    grep -rn "uvicorn\|gunicorn" --include="*.py" . | head -1
fi
```

### Step 3: Detect Service Dependencies

**Scan these sources for service dependencies:**

```bash
# Docker Compose (highest confidence)
if test -f docker-compose.yml; then
    # Extract service names and images
    grep -E "^\s+\w+:" docker-compose.yml | grep -v "version\|services"
    grep "image:" docker-compose.yml
fi

# Code imports (medium confidence)
# PostgreSQL
grep -rq "pgx\|pq\|database/sql.*postgres\|psycopg\|pg.*Pool\|sequelize.*postgres\|TypeORM.*postgres" . 2>/dev/null && echo "postgres detected"

# MySQL
grep -rq "mysql\|mariadb" --include="*.go" --include="*.py" --include="*.ts" . 2>/dev/null && echo "mysql detected"

# Redis
grep -rq "go-redis\|redigo\|redis\|ioredis\|bull" --include="*.go" --include="*.py" --include="*.ts" . 2>/dev/null && echo "redis detected"

# MongoDB
grep -rq "mongo\|bson\|mongoose" --include="*.go" --include="*.py" --include="*.ts" . 2>/dev/null && echo "mongodb detected"

# Kafka/RabbitMQ
grep -rq "kafka\|sarama\|confluent" . 2>/dev/null && echo "kafka detected"
grep -rq "rabbitmq\|amqp" . 2>/dev/null && echo "rabbitmq detected"
```

### Step 4: Detect Environment Variables

```bash
# Scan .env.example or .env.sample
if test -f .env.example; then
    cat .env.example
elif test -f .env.sample; then
    cat .env.sample
fi

# Scan code for env var references
# Go
grep -rn "os.Getenv\|os.LookupEnv\|viper.Get" --include="*.go" . 2>/dev/null | head -30

# Node.js
grep -rn "process.env\." --include="*.ts" --include="*.js" . 2>/dev/null | head -30

# Python
grep -rn "os.environ\|os.getenv\|settings\." --include="*.py" . 2>/dev/null | head -30

# Detect sensitive variables
grep -rEi "(PASSWORD|SECRET|KEY|TOKEN|CREDENTIAL|AUTH)" --include="*.go" --include="*.ts" --include="*.py" . 2>/dev/null | grep -i "getenv\|environ\|process\.env\|viper" | head -20
```

---

## Interactive Collection Flow (Mixed Mode)

### Decision Matrix: When to Ask vs When to Auto-Fill vs When to Write TODO

| Information | Detectable? | Critical? | Action |
|-------------|-------------|-----------|--------|
| Startup command | Often yes | **Yes** | Auto-detect → if fail, ask with the platform user-confirmation tool immediately |
| Health endpoint | Sometimes | **Yes** | Auto-detect → if fail, ask with the platform user-confirmation tool |
| Port | Usually | No | Auto-detect → default 8080 |
| Database type | Often | **Yes** (if code uses DB) | Auto-detect → if fail, ask with the platform user-confirmation tool |
| DB connection URL | No | **Yes** | Mark `requires_user_input`, use `${DATABASE_URL}` |
| Redis/cache | Sometimes | No | Auto-detect → write TODO if unclear |
| API keys | No | Depends | Mark `requires_user_input`, use `${VAR_NAME}` |
| Log level | Yes (default) | No | Auto-fill with "info" |
| Feature flags | Sometimes | No | Write TODO placeholder |

### User Confirmation Templates

In Codex, use `request_user_input`. On other platforms, use the equivalent user-confirmation
tool. If no confirmation tool is available, record assumptions and required follow-up in
`environment.json`.

**Template 1: Startup Command (critical, must ask if not detected)**

```json
{
  "question": "Unable to automatically detect how to start this project. How is the application started for development?",
  "header": "Startup",
  "options": [
    {
      "label": "Custom command",
      "description": "I'll provide the specific command (e.g., 'go run ./cmd/server', 'npm run dev')"
    },
    {
      "label": "Docker Compose",
      "description": "The project uses docker-compose up to start everything"
    },
    {
      "label": "Makefile target",
      "description": "There's a Makefile with run/start/dev targets"
    },
    {
      "label": "Not applicable",
      "description": "This is a library/package — no startup command needed"
    }
  ]
}
```

**Template 2: Database Dependency (critical if DB usage detected in code)**

```json
{
  "question": "Detected database usage in code ({detected_db_type}). Please confirm the database setup:",
  "header": "Database",
  "options": [
    {
      "label": "Docker container",
      "description": "Use Docker to run {db_type} locally (recommended for development)"
    },
    {
      "label": "Local installation",
      "description": "Database is installed directly on this machine"
    },
    {
      "label": "Remote/cloud",
      "description": "Database is hosted remotely (staging/dev environment)"
    },
    {
      "label": "SQLite/embedded",
      "description": "Use an embedded database for development/testing"
    }
  ]
}
```

**Template 3: Sensitive Configuration (always ask if detected)**

```json
{
  "question": "Detected sensitive environment variables in code: {var_list}. These are needed for the application to run. How should they be configured?",
  "header": "Secrets",
  "options": [
    {
      "label": "Environment variables (recommended)",
      "description": "Reference via ${VAR_NAME} — you set them in your shell profile"
    },
    {
      "label": "Safe test values available",
      "description": "Some of these have safe test/development values that can be used"
    },
    {
      "label": "Config file reference",
      "description": "Reference a local config file like ~/.config/app/secrets.json"
    },
    {
      "label": "Skip for now",
      "description": "Mark as TODO — fill in later before running verification"
    }
  ]
}
```

---

## Startup Scripts Generation

### setup-env.sh

```bash
#!/usr/bin/env bash
# Environment setup script — starts required services
# Generated by ecl-harness-engineer, consumed by harness-executor
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"

echo "=== Setting up environment for {project_name} ==="

# Start services via Docker (if docker-compose exists)
if [ -f "$PROJECT_ROOT/docker-compose.yml" ]; then
    echo "Starting services via docker-compose..."
    docker-compose -f "$PROJECT_ROOT/docker-compose.yml" up -d
fi

# Or start individual services
# {auto-generated based on detected services}

# Wait for services to be ready
echo "Waiting for services..."
# {auto-generated health checks}

# Run migrations (if applicable)
# {auto-generated migration commands}

echo "=== Environment ready ==="
```

### start-server.sh

```bash
#!/usr/bin/env bash
# Application startup script
# Generated by ecl-harness-engineer, consumed by harness-executor
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"

cd "$PROJECT_ROOT"

# Set default environment variables
export PORT="${PORT:-8080}"
export ENV="${ENV:-development}"
export LOG_LEVEL="${LOG_LEVEL:-debug}"

# Start the application
echo "Starting {project_name} on port $PORT..."
{startup_command}
```

### teardown-env.sh

```bash
#!/usr/bin/env bash
# Environment teardown script
# Generated by ecl-harness-engineer, consumed by harness-executor
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"

echo "=== Tearing down environment ==="

# Stop Docker services
if [ -f "$PROJECT_ROOT/docker-compose.yml" ]; then
    docker-compose -f "$PROJECT_ROOT/docker-compose.yml" down
fi

# Clean up optional runtime verification artifacts when advanced tracing is enabled
if [ -d "$PROJECT_ROOT/harness/trace" ]; then
    rm -rf "$PROJECT_ROOT/harness/trace/verify-report.json" 2>/dev/null
    rm -rf "$PROJECT_ROOT/harness/trace/verification-report.json" 2>/dev/null
fi

echo "=== Environment cleaned up ==="
```

---

## Readiness Check Types

| Type | When to Use | Config |
|------|-------------|--------|
| `http` | Web API with health endpoint | `{ "endpoint": "/health", "port": 8080, "expected_status": 200 }` |
| `tcp` | Service that listens on a port but no HTTP | `{ "host": "localhost", "port": 5432 }` |
| `log_pattern` | Service that logs a "ready" message | `{ "pattern": "Server listening on", "timeout_seconds": 30 }` |
| `process` | Just check the process is running | `{ "command": "pgrep -f 'my-app'" }` |
| `none` | Library or no startup needed | (omit readiness section) |

---

## Sensitive Configuration Security

> **Core rule: never hardcode sensitive values in environment.json or scripts.**

### Safe Patterns

| Pattern | Syntax | Example |
|---------|--------|---------|
| Environment variable | `${VAR_NAME}` | `"password": "${DB_PASSWORD}"` |
| With default | `${VAR_NAME:-default}` | `"port": "${PORT:-8080}"` |
| Config file ref | `$file:path:key` | `"key": "$file:~/.config/app/secrets.json:api.key"` |

### Detection and Marking

When sensitive variables are detected in code, mark them in `_meta.requires_user_input`:

```json
{
  "_meta": {
    "requires_user_input": ["DATABASE_URL", "JWT_SECRET", "API_KEY"],
    "todos": [
      "Set DATABASE_URL environment variable before running verification",
      "Configure JWT_SECRET for authentication testing"
    ]
  }
}
```

### What NOT to Put in environment.json

- Actual passwords, API keys, tokens
- Connection strings with embedded credentials
- Test credentials that might be valid
- Internal URLs that should not be in version control

---

## Autonomous Mode (User Confirmation Tool Not Available)

When no user-confirmation tool is available:

1. **Auto-detect everything possible** — use all detection strategies above
2. **Apply conservative defaults** — use most common values
3. **Mark unknowns as TODO** — never guess at critical config
4. **Document assumptions** — explain what was detected and what's assumed

```json
{
  "_meta": {
    "generated_by": "ecl-harness-engineer",
    "mode": "autonomous",
    "assumptions": [
      "Startup command inferred from cmd/server/main.go",
      "Port 8080 assumed (most common for Go web servers)",
      "PostgreSQL detected from pgx import in internal/storage/"
    ],
    "requires_user_input": ["DATABASE_URL"],
    "todos": [
      "Verify startup command is correct",
      "Confirm PostgreSQL connection details",
      "Set DATABASE_URL environment variable"
    ]
  }
}
```
