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
- 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:.
- File name:
config.yml or loopwright.yml (clearer when opened out of context)?
- 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
Summary
Replace
.loopwright/config.jsonwith 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
config.json, which today is entirely gate policy.config.jsonalready 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.ymlwith a top-levelversionand one section per feature.config.jsoncontent moves undergate:with no semantic change: same metrics, same limits, same baseline behavior.install.shmigrates an existingconfig.jsontoconfig.ymlonce, and the engine refuses to run with both present.Non-goals:
baseline.json. It is machine-written, never hand-edited, and stays JSON.Proposed approach
yamlpackage (YAML 1.2, so nono→falsesurprises) and validates each section against a schema, failing with exit code 2 and a clear message on an invalid file.config.default.jsonbecomesdefaults.yml, shipped with the engine.config.jsonexists andconfig.ymldoes not, the installer converts it (moving everything undergate:and turning//keys into comments), and leaves the old file renamed toconfig.json.bakfor review. About 44 references toconfig.jsonacross scripts, workflows and docs move to the loader.Alternatives considered
.loopwright.yml. More visible, but breaks the README promise that loopwright owns.loopwright/and nothing else.//key workaround is already in the file.Risks
config.json. Needs a fixture-tested converter and the.baksafety net.Task breakdown (link sub-issues)
To be produced by
/grill-rfc.Open questions
gate:.config.ymlorloopwright.yml(clearer when opened out of context)?gate:in a PR as something to flag in the PR comment, the same way it treats disabling a collector?Generated by Claude Code