# Diagnostics & Fix Plans — Structured Error Triage

> How zerolang surfaces compiler errors as machine-readable JSON with stable codes (NAM003, TYP009, TAR002, …), source spans, fixSafety levels, and repair ids. The agent-repair loop: check --json → explain <code> → fix --plan --json → apply the narrowest safe patch. Covers all fixSafety categories and when requires-human-review must block automation.

- Repository: vercel-labs/zerolang
- GitHub: https://github.com/vercel-labs/zerolang
- Human wiki: https://grok-wiki.com/public/wiki/vercel-labs-zerolang-9ab46b2a38e0
- Complete Markdown: https://grok-wiki.com/public/wiki/vercel-labs-zerolang-9ab46b2a38e0/llms-full.txt

## Source Files

- `skill-data/zero-diagnostics.md`
- `scripts/agent-repair-demo.mts`
- `native/zero-c/src/checker.c`
- `conformance/diagnostics`
- `conformance/provenance-surface.json`

---

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:

- [skill-data/zero-diagnostics.md](skill-data/zero-diagnostics.md)
- [scripts/agent-repair-demo.mts](scripts/agent-repair-demo.mts)
- [native/zero-c/src/main.c](native/zero-c/src/main.c)
- [conformance/diagnostics/unknown-name.expected.json](conformance/diagnostics/unknown-name.expected.json)
- [conformance/diagnostics/unknown-name.0](conformance/diagnostics/unknown-name.0)
- [conformance/provenance-surface.json](conformance/provenance-surface.json)
- [scripts/snapshot-command-contracts.mts](scripts/snapshot-command-contracts.mts)
- [scripts/zls.mts](scripts/zls.mts)
- [examples/agent-repair-demo/broken.0](examples/agent-repair-demo/broken.0)
</details>

# Diagnostics & Fix Plans — Structured Error Triage

Zerolang surfaces every compiler error — parse failures, type mismatches, import cycles, target capability gaps — as machine-readable JSON with stable alphanumeric codes, precise source spans, and attached repair metadata. This design makes the diagnostic stream the primary protocol between the compiler and any automated consumer: an editor plugin, a CI gate, or an autonomous coding agent. Human-readable prose is a fallback, not the source of truth.

The repair pipeline extends this contract one step further. After a check failure the agent can call `zero fix --plan --json` to receive a structured fix plan that classifies every candidate repair by its safety impact, pairs it with a `fixSafety` label, and explicitly marks cases that require human judgment before automation is allowed to write a file. This page explains the full flow from raw error output to safe patch application.

---

## Diagnostic JSON Shape

When any `zero` subcommand is run with `--json`, failures are returned to stdout as a self-describing envelope. The schema is versioned at `schemaVersion: 1`. The following fields are present on every diagnostic object inside the `diagnostics` array:

| Field | Type | Description |
|---|---|---|
| `code` | string | Stable alphanumeric code such as `NAM003` or `TYP009` |
| `severity` | string | Always `"error"` for check failures |
| `message` | string | Short human-readable summary |
| `path` | string | Source file path |
| `line` | number | 1-based line number |
| `column` | number | 1-based column number |
| `length` | number | Token width in characters |
| `expected` | string | Structured expected value (type, target, etc.) |
| `actual` | string | Structured actual value found |
| `help` | string | Concise next-action hint |
| `fixSafety` | string | Safety label for automated repair |
| `repair` | object | `{ id, summary }` — repair identifier and prose summary |
| `related` | array | Additional spans or cross-file facts |

Optional fields also appear for specific diagnostic classes:

- `borrowTrace` — borrow-checker diagnostics include the active borrow root, its binding, and the conflict location.
- `backendBlocker` — backend-blocked diagnostics include the backend name and the compilation stage that failed.

The JSON is assembled in `native/zero-c/src/main.c` inside `print_check_json` and `append_fix_plan_diagnostic`, which write each field in a defined order so downstream parsers can rely on stable key presence.

Sources: [native/zero-c/src/main.c:3130-3172]()

---

## Stable Diagnostic Codes

Diagnostic codes are derived from a numeric internal code via a single switch in `diag_code()`. The mapping is stable: the same internal code always yields the same string code across compiler versions.

```c
// native/zero-c/src/main.c:80-162 (excerpt)
case 3003: return "NAM003";   // unknown identifier
case 3010: return "TYP009";   // mutable storage required
case 6002: return "TAR002";   // capability unavailable for target
case 7001: return "IMP001";   // unknown package-local import
case 9001: return "PKG001";   // local dep path lacks zero.json
```

### Code Namespaces

| Prefix | Domain |
|---|---|
| `PAR` | Parser / syntax |
| `NAM` | Name resolution |
| `TYP` | Type system |
| `ERR` | Error/fallibility handling |
| `BOR` | Borrow checker |
| `OWN` | Ownership |
| `IMP` | Import resolution |
| `PKG` | Package graph |
| `TAR` | Target / capability |
| `BLD` | Build / backend |
| `STD` | Standard library contract |
| `IFC` | Interface constraints |
| `SHM`, `RCV` | Shape methods, receiver methods |
| `PUB`, `FLD` | Public API, field completeness |

The `explain` command exposes full documentation for any code in either prose or JSON:

```sh
zero explain TYP009
zero explain --json TYP009
```

The JSON form returns `code`, `title`, `description`, `rationale`, `help`, `bad`, `good`, and the same `repair` object that appears inside a diagnostic. This lets an agent fetch authoritative guidance before applying a patch without parsing terminal output.

Sources: [native/zero-c/src/main.c:80-163](), [native/zero-c/src/main.c:2740-2820]()

---

## Common Codes Reference

The following codes appear most frequently in agent triage workflows and are all covered by the embedded skill data:

| Code | Meaning | Default Repair ID |
|---|---|---|
| `NAM003` | Unknown identifier | `declare-missing-symbol` |
| `TYP009` | Mutable storage required (immutable binding passed to mutable API) | `make-binding-mutable` |
| `TAR001` | Unknown target name | `choose-target-with-required-capability` |
| `TAR002` | Capability unavailable for selected target | `choose-target-with-required-capability` |
| `IMP001` | Unknown package-local import | `fix-import-path` |
| `IMP002` | Package-local import cycle | `break-import-cycle` |
| `PKG001` | Local dependency path lacks `zero.json` | `fix-package-dependency-path` |
| `PKG002` | Package dependency cycle | `break-package-dependency-cycle` |
| `PKG003` | One package name resolves to conflicting versions | `choose-one-package-version` |
| `PKG004` | Selected target not supported by a dependency | `choose-target-compatible-package` |
| `BLD003` | Removed backend flag (`--emit c`) | `use-direct-emitter` |
| `ERR001` | `raises` annotation missing | `add-raises-or-rescue` |
| `ERR003` | Unchecked fallible call | `check-or-rescue-fallible-call` |
| `BOR001` | Borrow conflict | `end-conflicting-borrow` |
| `OWN001` | Use after move | `avoid-use-after-move` |

Sources: [skill-data/zero-diagnostics.md:49-68](), [native/zero-c/src/main.c:2591-2640]()

---

## Fix Safety Levels

Every diagnostic carries a `fixSafety` label. The same label is repeated in the `fixes` array of a `zero fix --plan --json` response. The compiler assigns safety at code-registration time inside `diag_fix_safety()`:

```c
// native/zero-c/src/main.c:2541-2588
static const char *diag_fix_safety(int code) {
  switch (code) {
    case 7001: case 7002: case 6002: case 9001: ...
      return "requires-human-review";
    case 1001: case 1002: case 1003:
      return "api-changing";
    case 3010: case 3011: case 3012: ... case 3049:
      return "behavior-preserving";
    default:
      return "requires-human-review";
  }
}
```

The fix plan envelope always enumerates the complete ordered list in `safetyLevels`:

```json
"safetyLevels": [
  "format-only",
  "behavior-preserving",
  "api-changing",
  "target-changing",
  "requires-human-review"
]
```

### Safety Category Definitions

| Category | Meaning | Can Automate? |
|---|---|---|
| `format-only` | Whitespace or trivia only; no semantic effect | Yes, safely |
| `behavior-preserving` | Compiler-verifiable: runtime behavior is unchanged | Yes, after span inspection |
| `api-changing` | Signatures, exports, or call sites change | Only with caller impact analysis |
| `target-changing` | Target support or capability use changes | Only after target matrix review |
| `requires-human-review` | Compiler cannot prove the edit is safe | Block automation; require explicit approval |

The default for unknown codes is `requires-human-review`, so any unrecognized diagnostic conservatively blocks automation.

Sources: [native/zero-c/src/main.c:2541-2588](), [native/zero-c/src/main.c:3214]()

---

## Fix Plan JSON: `zero fix --plan --json`

The fix plan command produces a separate JSON envelope from `zero check`. Its top-level fields are:

| Field | Value |
|---|---|
| `schemaVersion` | `1` |
| `ok` | `false` if any diagnostic is present |
| `mode` | `"plan"` |
| `appliesEdits` | `false` — the plan command never writes files |
| `safetyLevels` | Ordered enumeration of all safety categories |
| `input` | Source file path |
| `selfHostRepairPolicy` | Compiler's own policy for unsupported features and C-bridge fallback |
| `diagnostics` | Same diagnostic objects as in `zero check --json` |
| `fixes` | Array of fix entries: `{ id, diagnosticCode, safety, summary, appliesEdits }` |

The `fixes` array is the canonical list of recommended repairs. Each entry pairs a repair `id` with the `diagnosticCode` that triggered it, so an agent can join across `diagnostics` and `fixes` by code.

The `selfHostRepairPolicy` field signals the compiler's own migration constraints:

```json
"selfHostRepairPolicy": {
  "unsupportedFeatureSafety": "requires-human-review",
  "compatibilityFallback": "removed",
  "directFallback": "never-c-bridge"
}
```

This policy means the compiler never automatically falls back to C bridging; any such path requires human review.

Sources: [native/zero-c/src/main.c:3208-3230]()

---

## The Agent-Repair Loop

The canonical four-step loop demonstrated in `scripts/agent-repair-demo.mts` is:

```
check --json → explain <code> --json → fix --plan --json → inspect span → patch
```

```mermaid
sequenceDiagram
    participant Agent
    participant zero as zero CLI
    participant Source as Source File

    Agent->>zero: check --json <file>
    zero-->>Agent: { ok: false, diagnostics: [{ code, fixSafety, repair, line, column }] }
    Agent->>zero: explain --json <code>
    zero-->>Agent: { code, title, rationale, repair: { id, summary }, bad, good }
    Agent->>zero: fix --plan --json <file>
    zero-->>Agent: { mode: "plan", appliesEdits: false, fixes: [{ id, safety, summary }] }
    Agent->>Source: Read span at (line, column, length)
    Agent->>Source: Apply narrowest safe patch
    Agent->>zero: check --json <file>
    zero-->>Agent: { ok: true, diagnostics: [] }
```

### Step-by-Step

**1. Run `zero check --json`**

Parse the `diagnostics` array. Read `code`, `fixSafety`, `repair.id`, `line`, `column`, and `length`. Do not scrape stderr text; the JSON stream is the authoritative output.

**2. Run `zero explain --json <code>`**

Get the authoritative rationale and a concrete `bad`/`good` example pair for the code. This step is especially important before any broad refactor because the explain output confirms the compiler's intended fix direction.

**3. Run `zero fix --plan --json`**

Retrieve the structured fix plan. Read `fixes[].safety` and check it against the automation policy:
- `behavior-preserving` → inspect the span; apply if the edit matches the repair summary
- `api-changing` → check call sites; human sign-off recommended
- `requires-human-review` → block automation; surface to human reviewer

**4. Apply the narrowest safe patch**

Read only the source span identified by `(path, line, column, length)`. Apply only the edit that the repair summary describes. Re-run `zero check --json` to verify `ok: true` and `diagnostics: []`.

The demo in `scripts/agent-repair-demo.mts` shows this loop concretely for `TYP009` / `make-binding-mutable`:

```typescript
// scripts/agent-repair-demo.mts:23-38
const check = zeroJson(["check", "--json", workFile], true);
assert.equal(check.diagnostics[0].code, "TYP009");
assert.equal(check.diagnostics[0].repair.id, "make-binding-mutable");

const explain = zeroJson(["explain", "--json", "TYP009"]);
assert.equal(explain.repair.id, "make-binding-mutable");

const plan = zeroJson(["fix", "--plan", "--json", workFile]);
assert.equal(plan.mode, "plan");
assert.equal(plan.appliesEdits, false);
assert.equal(plan.fixes[0].id, "make-binding-mutable");

// Narrowest patch: only the one binding declaration changes
const fixedSource = brokenSource.replace("    let dst: [4]u8", "    let mut dst: [4]u8");
```

Sources: [scripts/agent-repair-demo.mts:23-41]()

---

## When `requires-human-review` Must Block Automation

The `requires-human-review` label is the compiler's explicit stop signal. It is assigned by default for unknown codes and explicitly for:

- All **import** and **package graph** errors (`IMP001`, `IMP002`, `PKG001`–`PKG004`): the fix involves structural decisions about module boundaries and version resolution that require human intent.
- **Target capability** errors (`TAR002`): moving code behind a target-specific entry point is an architectural change.
- **C interop** errors (`CIMP003`): configuring a target C dependency is environment-specific.
- **Build flag removal** (`BLD002`): migration off a removed flag may require pipeline changes.

The ZLS language server adapter in `scripts/zls.mts` propagates this label directly into the LSP code action payload:

```typescript
// scripts/zls.mts:293-301
.filter((diagnostic) => diagnostic.data?.repair?.id)
.map((diagnostic) => ({
  title: diagnostic.data.repair.summary ?? `Repair ${diagnostic.code}`,
  kind: "quickfix",
  data: {
    id: diagnostic.data.repair.id,
    safety: diagnostic.data.fixSafety ?? "requires-human-review",
  },
}))
```

The default when `fixSafety` is absent is `requires-human-review`, so the system is conservatively safe: unknown or missing safety labels never silently authorize automation.

Sources: [scripts/zls.mts:293-301](), [native/zero-c/src/main.c:2541-2588]()

---

## `diagnostic_can_apply_edits`: The Only Automatic Write Gate

The compiler contains exactly one code path that will actually write a file: `diagnostic_can_apply_edits`. It is gated to a single condition:

```c
// native/zero-c/src/main.c:3293-3294
static bool diagnostic_can_apply_edits(const ZDiag *diag) {
  return diag && diag->code == 3010 &&
         strcmp(diag_fix_safety(diag->code), "behavior-preserving") == 0;
}
```

Only `TYP009` (`make-binding-mutable`, internal code 3010) with a confirmed `behavior-preserving` safety label can pass this gate. All other repairs remain in plan-only mode (`appliesEdits: false`). This means `zero fix --plan --json` never edits files; only `zero fix --apply` can, and only for this one code.

Sources: [native/zero-c/src/main.c:3293-3319]()

---

## Conformance Test Coverage

The conformance suite verifies that every known diagnostic code produces the expected `fixSafety` and `repair.id` fields. The snapshot contract test in `scripts/snapshot-command-contracts.mts` runs `zero check --json` against a curated fixture for each code and asserts:

```typescript
// scripts/snapshot-command-contracts.mts:1213-1222
assert.equal(typeof diagnostic.fixSafety, "string");
assert.equal(typeof diagnostic.repair.id, "string");
assert.equal(typeof diagnostic.expected, "string");
assert.equal(typeof diagnostic.actual, "string");
assert.equal(Array.isArray(diagnostic.related), true);
```

The fixture list covers `PAR100`, `NAM003`, `IMP001`, `TYP009`, `BOR001`, `ERR001`–`ERR003`, `ABI001`, `PUB001`, `IFC002`, `SHM001`, `RCV001`, `RCV002`, and more. This ensures the JSON contract is regression-tested for every code namespace.

The `conformance/diagnostics/` directory additionally stores per-code expected JSON blobs. For example, the `NAM003` fixture confirms `fixSafety: "local-edit"` in its expected output:

```json
// conformance/diagnostics/unknown-name.expected.json
{
  "schemaVersion": 1,
  "code": "NAM003",
  "title": "Unknown identifier",
  "fixSafety": "local-edit",
  "defaultOutput": "plain-text-no-ansi"
}
```

Sources: [scripts/snapshot-command-contracts.mts:1185-1228](), [conformance/diagnostics/unknown-name.expected.json:1-7]()

---

## Summary

Zerolang's diagnostic system is built around a single invariant: every compiler error is addressable by a stable code, locatable by a precise span, and annotatable with a machine-readable safety label. The agent-repair loop — `check --json` → `explain --json` → `fix --plan --json` → apply the narrowest patch → `check --json` — is the sanctioned automation path. The `fixSafety` field is the primary gate: `behavior-preserving` permits narrow automated edits; `requires-human-review` blocks them unconditionally, with a conservative default of blocking for any code the compiler has not explicitly classified. The only file-writing path in the entire compiler is `diagnostic_can_apply_edits`, which is restricted to a single code under a confirmed safe label.

Sources: [native/zero-c/src/main.c:3293-3294]()
