Skip to content

RFC: One config file, YAML, sectioned by feature #8

Description

@SamuelDenani

Summary

Replace .loopwright/config.json with a single .loopwright/config.yml, organized in one section per feature (gate, toolchain, runtime, changesets, and whatever comes next). Every feature reads its own section, the installer and the future TUI write it, and loopwright keeps owning only .loopwright/.

Motivation

  • The upcoming RFCs each add harness-level choices: toolchain provider (connectors), runtime level (runtime), automatic changesets (changesets). Without a plan, each would grow its own file or squeeze into config.json, which today is entirely gate policy.
  • One file sectioned by feature keeps every choice in one place, and the section boundaries mirror the principles RFC: each feature is a detail with a default, and its section is where you swap it.
  • config.json already fakes comments with keys like "//audit" and "//limits". Those explanations are real documentation for whoever tunes the gate; YAML makes them actual comments.

Goals / Non-goals

Goals:

  • .loopwright/config.yml with a top-level version and one section per feature.
  • The current config.json content moves under gate: with no semantic change: same metrics, same limits, same baseline behavior.
  • Defaults shipped with the engine, so a section the user never wrote still has a value.
  • install.sh migrates an existing config.json to config.yml once, and the engine refuses to run with both present.

Non-goals:

  • Changing any gate semantics.
  • baseline.json. It is machine-written, never hand-edited, and stays JSON.
  • The interactive installer (separate draft RFC).

Proposed approach

version: 1

gate:
  sources:
    roots: [src]
    extensions: [.ts, .tsx]
  collectors:
    lint: { adapter: eslint }
    tests: { adapter: vitest, command: vitest run --coverage }
  # Per-file and per-function shape limits. Code over a limit at baseline
  # time is grandfathered and only blocks if it gets worse.
  limits:
    complexity: { warn: 10, block: 15 }
  metrics:
    coverage.lines: { direction: higher-better, hardMin: 80, tolerance: 0.5, onRegression: block }
  audit:
    ignore: []

toolchain:
  provider: mise

runtime:
  level: container

changesets:
  auto: true
  • Parsing: the engine adds the yaml package (YAML 1.2, so no no → false surprises) and validates each section against a schema, failing with exit code 2 and a clear message on an invalid file.
  • Defaults: config.default.json becomes defaults.yml, shipped with the engine.
  • Section ownership: each feature reads only its own section through one config loader. No feature reads another feature's section directly.
  • Migration: on update, if config.json exists and config.yml does not, the installer converts it (moving everything under gate: and turning // keys into comments), and leaves the old file renamed to config.json.bak for review. About 44 references to config.json across scripts, workflows and docs move to the loader.

Alternatives considered

  • Two files (gate policy + harness settings). Rejected: two places to look, two schemas, two migration paths.
  • Root .loopwright.yml. More visible, but breaks the README promise that loopwright owns .loopwright/ and nothing else.
  • Keep JSON. No new dependency, but no comments, and the // key workaround is already in the file.
  • TOML. Real comments and stricter than YAML, but deeply nested metric tables read worse than in YAML.

Risks

  • Silent gate tightening through defaults. If missing keys fall back to engine defaults, a loopwright update that changes a default metric changes a host's gate without any PR in the host. See open question 1; this is the decision that matters most here.
  • YAML footguns: indentation errors and ambiguous scalars. Mitigated by schema validation and failing loudly.
  • Migration on hosts with hand-edited config.json. Needs a fixture-tested converter and the .bak safety net.

Task breakdown (link sub-issues)

To be produced by /grill-rfc.

Open questions

  1. Overlay or full copy for the gate section? Overlay (the file only holds overrides, engine defaults fill the rest) keeps the file short but lets updates move a host's gate. Full copy (today's behavior: detect-stack writes everything) is explicit but long. A middle ground: overlay for feature sections, full copy for gate:.
  2. File name: config.yml or loopwright.yml (clearer when opened out of context)?
  3. Does the gate treat a change to gate: in a PR as something to flag in the PR comment, the same way it treats disabling a collector?

Generated by Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    rfcRFC: top-level design and intent

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions