# Getting Started with efix

efix is a GitHub Action and local CLI that takes a failing reproduction command, runs an evidence-first fix protocol, and opens a pull request with before/after evidence, mandatory tests, and a structured PR body.

---

## 1. Prerequisites

| Requirement | Notes |
|---|---|
| Node.js 22+ | Required for `--experimental-strip-types` (no build step) |
| git | Must be installed and on `PATH`; used for diff, patch apply, and branch management |
| OpenAI API key | Set as `OPENAI_API_KEY` in your environment or as a repository secret |
| A git repository | The target repo must be initialized as a git repo (`git rev-parse` must succeed) |

The tool ships with zero npm dependencies. No `npm install` is required in the veri-pr-agent repo itself.

---

## 2. Installation

efix runs as a GitHub Action in your target repository. Copy the workflow file into the repo you want to enable it on:

```bash
mkdir -p /path/to/your-repo/.github/workflows
cp /path/to/veri-pr-agent/.github/workflows/efix.yml \
   /path/to/your-repo/.github/workflows/efix.yml
```

Then copy the CLI source into the same repo or reference it from a central location. The workflow expects `src/cli.ts` to be present at `$GITHUB_WORKSPACE/src/cli.ts` (i.e., the workflow runs in the same repository that contains the efix source).

For the current MVP, efix is colocated with the target repository. The expected layout:

```
your-repo/
  .github/workflows/efix.yml
  .efix.yml                  ← your config (copied from .efix.yml.example)
  src/cli.ts                 ← efix CLI source
  src/...                    ← rest of efix source
  artifacts/                 ← written at runtime; gitignore this
```

Add repository secrets in **Settings > Secrets and variables > Actions**:

| Secret | Value |
|---|---|
| `OPENAI_API_KEY` | Your OpenAI API key |

---

## 3. Configuration

Copy `.efix.yml.example` to `.efix.yml` in your target repository and adjust the values for your project:

```yaml
# .efix.yml

# Engine to use. MVP only supports "openai".
engine: openai

openai:
  # Model name. gpt-4.1-mini is the default; use gpt-4o for higher quality.
  model: gpt-4.1-mini

  # Name of the environment variable holding the OpenAI API key.
  api_key_env: OPENAI_API_KEY

  # Milliseconds before an individual OpenAI request is aborted.
  timeout_ms: 120000

  # How many times to retry a failed or invalid structured response.
  max_retries: 2

test:
  # If true, the run fails when code changes without a corresponding test file change.
  required: true

  # Commands to run during verification. If empty, the repro command is re-run instead.
  required_suites:
    - "npm test"

diff:
  # Maximum number of files the patch may touch before justification is required.
  max_files: 8

  # Maximum total lines added + removed before justification is required.
  max_loc: 300

commands:
  # Allowlist of command prefixes or exact commands the agent and repro step may run.
  # Entries without spaces match on the leading token; entries with spaces require
  # an exact prefix match on the full command string.
  allow:
    - npm
    - npm test
    - npm run test
    - node
    - npx

  # Per-command timeout for reproduction and verification shell commands.
  timeout_ms: 600000

security:
  # "deny" blocks curl, wget, scp, ssh, nc, netcat.
  # "allow" permits all commands that pass the allowlist.
  network: deny

  # Environment variable names whose runtime values are redacted from all artifacts.
  redact:
    - GITHUB_TOKEN
    - OPENAI_API_KEY
```

The config file is optional. If `.efix.yml` is not found, the orchestrator uses built-in defaults and logs a note to the evidence artifact.

---

## 4. First Run — workflow_dispatch

1. Navigate to your repository on GitHub.
2. Click the **Actions** tab.
3. Select the **efix** workflow from the left sidebar.
4. Click **Run workflow**.
5. Fill in the inputs:
   - **Failing reproduction command** (required): the exact shell command that currently fails, e.g. `npm test -- --testNamePattern "auth should reject invalid token"`
   - **Optional issue or PR number**: if set, efix posts a comment on that issue/PR when it finishes.
   - **Engine**: leave as `openai`.
6. Click **Run workflow**.

The workflow runs these steps in order:

1. Checks out the repository with full history (`fetch-depth: 0`).
2. Sets up Node 22.
3. Installs your project's dependencies (`npm ci`, `pnpm install`, or `yarn install` depending on which lockfile is present).
4. Runs efix: reproduces the failure, generates a patch, applies it, verifies it, and writes artifacts.
5. Uploads `artifacts/` as a GitHub Actions artifact named `efix-artifacts`.
6. If the run succeeds and produces changes, creates a branch `efix/<run-id>-<attempt>` and opens a pull request.
7. If triggered by a PR comment, posts a comment on the originating PR with a link to the new PR.
8. If the run fails, posts a failure summary comment (if an issue/PR number was provided).

---

## 5. Comment Trigger — `/efix <command>`

Any PR comment from a trusted collaborator (`OWNER`, `MEMBER`, or `COLLABORATOR`) containing `/efix` followed by a shell command triggers efix automatically:

```
/efix npm test -- --testNamePattern "user login fails with bad password"
```

The workflow parses the first `/efix <command>` occurrence on a single line and uses the text after `/efix ` on that line as the reproduction command. The PR number is captured automatically from the comment context.

Requirements:
- The comment must be on a pull request (not a plain issue).
- The PR head branch must be in the same repository (fork PRs are not supported in the current workflow).
- The commenter must have `OWNER`, `MEMBER`, or `COLLABORATOR` association on the repository.
- The comment body must contain a valid `/efix <command>` invocation.
- The text immediately following `/efix ` on the matched line is used as the repro command. Make sure the command is allowed by `commands.allow` in your `.efix.yml`.

When the run completes, efix posts a reply comment on the same PR:

```
efix completed successfully. Opened PR #42: https://github.com/org/repo/pull/42
```

If the run fails, it posts a collapsible failure details comment with a link to the workflow run.

---

## 6. What You Will See — PR Body Sections

A successful run opens a PR whose body contains the following sections:

**Problem**
The stderr or stdout from the failing reproduction command, or a reference to evidence artifacts if the output was empty.

**Reproduction**
The exact repro command that was run and its exit code.

**Root Cause**
The AI's analysis of why the failure occurred, based on observed command output and the identified candidate files.

**Fix**
A plain-language description of the change made. The corresponding diff is applied to the branch.

**Tests Added/Updated**
Either a description of what tests were added or updated, or an explicit exception if the mandatory test policy granted an exemption (e.g., docs-only change, no test harness detected).

**Evidence (Before/After)**
Captured command output from before the fix (reproduction phase) and after (verification phase), truncated to 1,200 characters. Full output is available in `artifacts/evidence.md` and `artifacts/evidence.json`.

**Risk / Rollback**
The AI's assessment of risk and instructions for reverting the change if needed.

**Diff Summary**
File count and lines added/removed for the patch.

The PR title follows the format `efix: <short description of fix>`.

---

## 7. Troubleshooting

### "Command not allowed by policy: \<command\>"

Your repro command or a verification command is not in `commands.allow`. The allowlist uses prefix matching on the leading shell token for single-word entries, and exact prefix matching for entries that contain spaces.

To fix: add the required command or prefix to `commands.allow` in `.efix.yml`:

```yaml
commands:
  allow:
    - npm
    - npm test
    - pnpm          # add pnpm if your project uses it
    - pnpm test
    - vitest        # add vitest if used directly
```

Note: `curl`, `wget`, `scp`, `ssh`, `nc`, and `netcat` are additionally blocked when `security.network: deny` regardless of the allowlist.

---

### "Diff policy failed"

The patch the AI generated exceeds `diff.max_files` or `diff.max_loc` and the model did not provide per-file justifications for every touched file.

Options:

1. Raise the thresholds in `.efix.yml`:

   ```yaml
   diff:
     max_files: 15
     max_loc: 600
   ```

2. Narrow the repro command to produce a more focused failure signal so the AI targets fewer files.

3. The AI can pass this check even over the limits if it provides a `fileJustifications` array in its response covering every changed file. If the model is capable of this, no config change is needed — retrying may produce a compliant response.

---

### "Mandatory test policy failed"

This fires when all of the following are true simultaneously:

- `test.required: true` in config (the default).
- At least one non-docs file was changed by the patch.
- A test harness was detected (a `package.json` test script, `pytest.ini`, `tox.ini`, or `pyproject.toml` found in the repo root).
- The patch did not include any file matching the test file heuristic (`__tests__/`, `tests/`, `.test.*`, `.spec.*`).
- No test command (`test`, `vitest`, `jest`, `pytest`, `mocha`, or `ava`) was among the verification commands that ran.

Automatic exceptions that suppress the check:

| Exception | Condition |
|---|---|
| `test.required: false` | Config explicitly disables the policy |
| Docs-only change | Every changed file has a `.md`, `.mdx`, `.txt`, `.rst`, or `.adoc` extension, or lives under `docs/` |
| No harness detected | None of the harness indicator files exist in the repo root |
| No changes produced | The patch was empty |

If you hit this error on a legitimate change, check that:
- Your `required_suites` list includes a command containing one of the test keywords above.
- The patch actually touched a file inside `__tests__/`, `tests/`, or with a `.test.*` / `.spec.*` filename. If the AI did not add tests, refine the repro command to produce a more specific failure.

---

### OpenAI API Errors

| Error pattern | Likely cause | Resolution |
|---|---|---|
| `Missing OpenAI API key env var: OPENAI_API_KEY` | Secret not set | Add `OPENAI_API_KEY` to repo secrets |
| `OpenAI API error: 401` | Invalid or expired key | Rotate your API key |
| `OpenAI API error: 429` | Rate limit exceeded | The run retries automatically (up to `max_retries` times); reduce concurrency or upgrade tier |
| `OpenAI API error: 5xx` | OpenAI service error | Retried automatically; check status.openai.com if persistent |
| `OpenAI request timed out` | Request exceeded `timeout_ms` | Increase `openai.timeout_ms` or use a faster model |
| `OpenAI plan_response validation failed` | Model returned malformed JSON | Increase `max_retries`; switch to a more capable model |
| `Patch validation failed (git apply --check)` | The diff the model produced does not apply cleanly | Usually indicates stale file contents in the model's context; retry the run |

---

## 8. Configuration Reference

All fields are optional if `.efix.yml` is absent; the defaults shown below apply.

### Top-level

| Field | Type | Default | Description |
|---|---|---|---|
| `engine` | string | `"openai"` | Engine to use. MVP only supports `"openai"`. |

### `openai`

| Field | Type | Default | Description |
|---|---|---|---|
| `model` | string | `"gpt-4.1-mini"` | OpenAI model name passed to `/v1/chat/completions`. |
| `api_key_env` | string | `"OPENAI_API_KEY"` | Name of the environment variable holding the API key. |
| `timeout_ms` | number | `120000` | Per-request timeout in milliseconds. Minimum: `1000`. |
| `max_retries` | number | `2` | Number of retry attempts for failed or invalid structured responses. Minimum: `0`. |
| `base_url` | string | `"https://api.openai.com"` | Base URL for the OpenAI-compatible API. Override to use a proxy or alternative endpoint. |

### `test`

| Field | Type | Default | Description |
|---|---|---|---|
| `required` | boolean | `true` | Whether code changes must include a test file change. Set to `false` to disable the policy globally for this repo. |
| `required_suites` | string[] | `[]` | Commands to run during verification. If empty, the original repro command is re-run. Each command must be in `commands.allow`. |

### `diff`

| Field | Type | Default | Description |
|---|---|---|---|
| `max_files` | number | `8` | Maximum number of files the patch may touch. Must be >= 1. |
| `max_loc` | number | `300` | Maximum total lines added + removed. Must be >= 1. |

### `commands`

| Field | Type | Default | Description |
|---|---|---|---|
| `allow` | string[] | `["node","npm","npx","pnpm","yarn","pytest"]` | Allowlist of permitted command prefixes. Must not be empty. Single-word entries match the leading shell token; entries containing spaces require an exact prefix match on the full command string. |
| `timeout_ms` | number | `600000` | Per-command timeout in milliseconds for reproduction and verification shell commands. Minimum: `1000`. |

### `security`

| Field | Type | Default | Description |
|---|---|---|---|
| `network` | `"deny"` or `"allow"` | `"deny"` | When `"deny"`, any command whose leading token is `curl`, `wget`, `scp`, `ssh`, `nc`, or `netcat` is blocked regardless of the allowlist. |
| `redact` | string[] | `["GITHUB_TOKEN","OPENAI_API_KEY"]` | Names of environment variables whose runtime values are replaced with `[REDACTED]` in all artifact outputs. |
