# ADR-0001: YAML as Primary Human Data Exchange Format

## Status

✅ Accepted

## Context

omni-dev produces structured output for users to read (commit views, check reports) and
generates intermediate files that users may inspect or edit before the tool reads them back
(amendment files, PR content). It also consumes user-authored configuration (scopes, model
registry) and communicates with AI providers over HTTP.

These scenarios have fundamentally different requirements:

- **Human-facing data** needs to be easy to read at a glance and comfortable to edit in any
  text editor. Multiline content — commit messages, diffs, PR descriptions — must remain
  legible without escaping.
- **Machine interoperability** needs to match the conventions of the systems being integrated
  with: JSON for HTTP APIs, JSON for settings files that mirror the conventions of tools like
  Claude Code, and whatever format external CLIs such as `gh` emit.

JSON is the dominant format for machine-to-machine exchange but is hostile to multiline
strings. It requires `\n` escaping, which makes commit messages and diffs unreadable in raw
form. TOML handles multiline strings but becomes unwieldy for deeply nested or
heterogeneous structures like commit metadata with optional fields.

YAML's literal block scalar syntax (`|`) preserves multiline content verbatim, making commit
messages, diffs, and PR descriptions readable without any escaping. Its minimal punctuation
also makes it more comfortable to hand-edit than JSON.

## Decision

We will use YAML as the primary format whenever structured data is intended for human
consumption — whether the human is reading, writing, or both.

Other formats are used where interoperability dictates:

### YAML (human-facing)

- **CLI output** — the `view` command emits YAML for commit and branch data.
- **Amendment files** — AI-generated commit message suggestions are written to
  `.ai/scratch/amendments-*.yaml` so users can review and edit before applying.
- **PR round-trip files** — PR content is serialised to `pr-details.yaml`, optionally edited,
  then parsed back.
- **AI response parsing** — AI models are instructed to return YAML (`title` +
  `description`) so the response is both machine-parseable and human-readable in logs.
- **Check reports** — available via `--format yaml` on the `check` command.
- **Configuration** — `scopes.yaml` and `models.yaml` are user-editable project
  configuration.

### JSON (interoperability)

- **Settings** — `~/.omni-dev/settings.json` uses JSON to match the convention established
  by Claude Code's own settings files.
- **HTTP APIs** — all AI provider requests and responses use JSON on the wire (Anthropic,
  OpenAI, Bedrock), as required by their APIs.
- **GitHub CLI** — `gh pr view --json` output is parsed as JSON because that is what `gh`
  emits.
- **Check reports** — available via `--format json` on the `check` command for CI/CD
  pipeline integration.

### Markdown (human-authored prose)

- **Guidelines** — `commit-guidelines.md`, `pr-guidelines.md`, and
  `pull_request_template.md` are prose documents authored by humans.
- **Claude command templates** — generated `.claude/commands/*.md` files are Markdown because
  Claude Code expects that format.

### Library choices

Three Rust crates handle serialisation, each chosen for a specific capability:

- **`serde_yaml` (0.9)** — used for all *deserialisation* (`from_str`) and for the initial
  serialisation step (`to_value`). Chosen because it integrates with serde's derive macros
  (`#[derive(Serialize, Deserialize)]`), which means data structures are defined once and
  work across both YAML and JSON. However, its emitter does not support literal block
  scalars — multiline strings are emitted as quoted strings with `\n`, defeating the
  readability goal.

- **`yaml-rust-davvid` (0.6)** — used for the final *serialisation* step (emitting YAML
  text). This is a maintained fork of the original `yaml-rust` crate. Its `YamlEmitter`
  supports `multiline_strings(true)`, which emits multiline values using YAML literal block
  scalar syntax (`|`). This is the specific feature that makes the output human-readable.
  The trade-off is a two-step serialisation pipeline: `serde_yaml::to_value` → convert to
  `yaml_rust_davvid::Yaml` nodes → `YamlEmitter::dump`. This conversion logic lives in
  `src/data/yaml.rs`.

- **`serde_json` (1.0)** — used for JSON interoperability: parsing `settings.json`,
  serialising `check` reports in `--format json`, parsing `gh` CLI output, and HTTP
  request/response bodies via `reqwest`'s `.json()` methods. Chosen as the de facto
  standard Rust JSON library with serde integration.

## Consequences

- Users can read structured output directly in the terminal without mentally un-escaping
  `\n` sequences.
- Amendment and PR files are comfortable to hand-edit in any text editor, enabling a
  human-in-the-loop workflow.
- AI responses logged or written to scratch files are immediately legible for debugging.
- The dual-library serialisation approach adds complexity to `src/data/yaml.rs`, but this
  is confined to a single module.
- The `check` command supports all three formats (`text`, `json`, `yaml`) to serve both
  human and CI/CD consumers. Other commands emit YAML only, which is sufficient given their
  primary audience is humans.
- Interoperability formats (JSON for APIs and settings, Markdown for prose) are used where
  the external system dictates, avoiding unnecessary format translation.
