Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
f981739
docs: add spec for #11
SamuelDenani Oct 5, 2026
cbf6eb8
docs: add principles doc skeleton and context
SamuelDenani Oct 5, 2026
2280f70
fix: tighten principles Context claims (#11)
SamuelDenani Oct 5, 2026
8348fc5
docs: add the twelve core opinions with rejects lines (#11)
SamuelDenani Oct 7, 2026
07547ec
docs: add the rationale prose under the core opinions (#11)
SamuelDenani Oct 7, 2026
835971a
docs: add the detail territory and the agent-host contract (#11)
SamuelDenani Oct 7, 2026
d601107
fix: correct detail territory claims (#11)
SamuelDenani Oct 7, 2026
f38fa53
fix: rename the territory default column and define a default (#11)
SamuelDenani Oct 7, 2026
5870e63
docs: add the classification rule and the amendment mechanism (#11)
SamuelDenani Oct 7, 2026
b7b01be
docs: reconcile the principles doc with RFCs #6-#10
SamuelDenani Oct 7, 2026
7a4274d
fix: restore local session as T3's default and tighten the default ru…
SamuelDenani Oct 7, 2026
2ece483
fix: correct the swap rule, empty-pointer, baseline and capability wo…
SamuelDenani Oct 7, 2026
8c45702
docs: fix P10 pointer claim, backtick changesets, name swap-rule exce…
SamuelDenani Oct 7, 2026
437653e
Merge feat/rfc-5-core-principles into task/11-principles-doc
SamuelDenani Oct 7, 2026
26681fe
Merge pull request #14 from SamuelDenani/task/11-principles-doc
SamuelDenani Oct 7, 2026
17d981a
docs: add spec for #12
SamuelDenani Oct 7, 2026
8ba70bf
docs: add spec for #13
SamuelDenani Oct 7, 2026
c01c0ee
docs: lead the README with the process opinion, not the stack (#13)
SamuelDenani Oct 7, 2026
be9571f
docs: reconcile the README merge rule with P8 and wire in the princip…
SamuelDenani Oct 7, 2026
ab9ee43
docs: mark the missing merge opt-in as a present-state fact (#13)
SamuelDenani Oct 7, 2026
da0bf75
docs: introduce loopwright to host repos by its opinions first (#13)
SamuelDenani Oct 7, 2026
abf29b0
chore: describe loopwright by its process opinions (#13)
SamuelDenani Oct 7, 2026
27edb2b
docs: define this repo by the opinions it is held to, not its directo…
SamuelDenani Oct 7, 2026
c60d8d4
docs: stop attributing every opinion's enforcement to the engine dir …
SamuelDenani Oct 7, 2026
611bc5f
docs: name both halves of loopwright in the README lead (#13)
SamuelDenani Oct 7, 2026
4a9f175
Merge pull request #17 from SamuelDenani/task/13-principles-first-ide…
SamuelDenani Oct 7, 2026
40861c2
docs: strip the opinions from loop-harness, leaving mechanics (#12)
SamuelDenani Oct 8, 2026
83bfcb8
Merge pull request #18 from SamuelDenani/task/12-strip-harness-opinions
SamuelDenani Oct 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions .loopwright/claude-md-section.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
## loopwright

This repo has the loopwright layer vendored under `.loopwright/`: RFC-driven
issues, an agentic execution loop, and a CI quality gate. See
`docs/loopwright/quality-gate.md` for how the gate works, and
In this repo, "done" is a mechanical verdict from configured checks, never
the judgement of whoever merges: quality is a ratchet against a committed
baseline, and a green build has to be earned rather than bought. Those
opinions are enforced by the loopwright layer vendored under `.loopwright/`:
RFC-driven issues, an agentic execution loop, and a CI quality gate. Read
`docs/loopwright/principles.md` for the opinions in full and what each one
refuses, `docs/loopwright/quality-gate.md` for how a verdict is reached, and
`docs/loopwright/loop-harness.md` for how work flows from an RFC issue to a
merged PR (the `/grill-rfc`, `/execute-issue`, and `/babysit-pr` skills).

Expand Down
16 changes: 11 additions & 5 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
# loopwright

Built from the loopwright project — this repo IS the product: the engine
lives in `.loopwright/scripts/`, its tests in `.loopwright/tests/`
This repo is loopwright itself, so nothing in it may make "done" a judgement
call or buy a green build — it is held to the opinions it ships
(`docs/loopwright/principles.md`). Its engine lives in
`.loopwright/scripts/`, its tests in `.loopwright/tests/`
(`cd .loopwright && npx vitest run` runs them).

<!-- loopwright:start -->
## loopwright

This repo has the loopwright layer vendored under `.loopwright/`: RFC-driven
issues, an agentic execution loop, and a CI quality gate. See
`docs/loopwright/quality-gate.md` for how the gate works, and
In this repo, "done" is a mechanical verdict from configured checks, never
the judgement of whoever merges: quality is a ratchet against a committed
baseline, and a green build has to be earned rather than bought. Those
opinions are enforced by the loopwright layer vendored under `.loopwright/`:
RFC-driven issues, an agentic execution loop, and a CI quality gate. Read
`docs/loopwright/principles.md` for the opinions in full and what each one
refuses, `docs/loopwright/quality-gate.md` for how a verdict is reached, and
`docs/loopwright/loop-harness.md` for how work flows from an RFC issue to a
merged PR (the `/grill-rfc`, `/execute-issue`, and `/babysit-pr` skills).

Expand Down
27 changes: 18 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
# loopwright

**A CI quality gate for repos where agents write the code — plus the loop that
feeds it.** Vendor it into any JS/TS repo: every PR gets scored against a
committed baseline, and the verdict comes back as a comment an agent can act on
without a human translating it first.
**"Done" is a mechanical verdict, not a judgement call.** loopwright is a CI
gate plus the loop that feeds it: it scores every PR against a committed
baseline, asks whether the green was earned or bought, and posts the verdict
as a comment an agent can act on without a human translating it first.
Vendor it into any JS/TS repo: it is opinionated about how work flows and how
"done" is judged, and agnostic about the tools that measure it.

> *A wright is a maker — a shipwright builds ships, a playwright builds plays.
> A loopwright builds loops.*
Expand Down Expand Up @@ -86,9 +88,11 @@ stack stays yours: loopwright owns `.loopwright/` and nothing else.
get the engine, its 148 tests and its docs as a starting point to modify. Point
`sources.roots` in `.loopwright/config.json` at your code and re-baseline.

## Your stack, not loopwright's
## Your toolchain is a detail

Each collector picks an adapter, and the installer detects which one you're on:
loopwright fixes the metric vocabulary and the verdict, never which tool
measures each metric — each collector picks an adapter, and the installer
detects which one you're on:

| Collector | Adapters |
|---|---|
Expand Down Expand Up @@ -139,8 +143,11 @@ issue, a committed spec in `docs/specs/`, one commit per plan step, the PR
conversation — so any session or human can pick up a half-finished task from
the repo alone.

Two rules hold the whole thing together: **the gate is the only source of truth
for "done"**, and **merging is always the human's decision.**
Two rules hold the whole thing together: **"done" is the gate's verdict,
never the judgement of whoever merges**, and **an irreversible action
needs a human unless automating it has been explicitly opted into** —
loopwright ships no such opt-in today, so nothing in the harness merges. Both
are stated in full in `docs/loopwright/principles.md` (P2 and P8).

## Running it

Expand All @@ -159,6 +166,7 @@ authoritative verdict.

| Piece | Where |
|---|---|
| Principles — what is fixed, what is swappable | `docs/loopwright/principles.md` |
| Flow overview | `docs/loopwright/loop-harness.md` |
| Gate design, metric by metric | `docs/loopwright/quality-gate.md` |
| Gate engine + adapters | `.loopwright/scripts/` |
Expand All @@ -170,7 +178,8 @@ authoritative verdict.

## Requirements

- Node ≥ 20.11 and a `package.json` (loopwright targets JS/TS repos)
- Node ≥ 20.11 and a `package.json` — the engine runs on Node, and
today's adapters measure a JS/TS repo
- npm, pnpm or yarn — the engine and its CI workflow detect which from your lockfile
- [`gh`](https://cli.github.com), authenticated
- [Claude Code](https://claude.com/claude-code) locally, and the
Expand Down
32 changes: 20 additions & 12 deletions docs/loopwright/loop-harness.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

How work flows through this repo when coding agents execute it. The quality
gate (`docs/loopwright/quality-gate.md`) is the enforcement half; this is the
execution half.
execution half. The opinions both halves serve are stated in
`docs/loopwright/principles.md`.

## Flow

Expand Down Expand Up @@ -38,10 +39,10 @@ Draft PR
│ gate green, draft → promote to ready (triggers Claude review)
│ review findings → fix or reply with reasons, never ignore
▼
Ready PR, gate green, reviews addressed → human merges
Ready PR, gate green, reviews addressed → merge
```

## Rules of the loop
## Mechanics

- **Base branch**: a task with no open `blockedBy` always branches from
`origin/main`. A task inside a feature-branch RFC branches from
Expand All @@ -55,15 +56,10 @@ Ready PR, gate green, reviews addressed → human merges
- **Draft until ready**: PRs open as drafts. The quality gate runs on drafts
(cheap, mechanical signal for the babysitting loop); the Claude review
workflow skips drafts and runs on `ready_for_review`.
- **The gate is the only source of truth for "done".** No agent may override
a red verdict, regenerate `.loopwright/baseline.json`, or take any of the
gated shortcuts listed in `CLAUDE.md`.
- **State lives in artifacts, not sessions**: the refined RFC and its comment
trail on the issue, the committed spec in `docs/specs/`, per-step commits,
and the PR conversation. Any session (or human) can pick up a half-done
task from these alone — except the grill phase itself, whose task spine and
drafts are session-scoped, so a crashed grill restarts.
- **Merging is always the human's decision.**
- **The grill phase is session-scoped.** `/grill-rfc` holds its task spine
and its sub-issue drafts in the session that runs it: the spine is a native
task list, the drafts are `draft-<slug>.md` files in a scratch directory,
and neither is committed. A crashed grill restarts.
- **Architecture is scaffolding, not a spec.** The architect phase's mermaid
lives in an RFC issue comment and never in the issue body, because
`planner.md` reads the body via `gh issue view` and comments are not
Expand All @@ -76,6 +72,18 @@ Ready PR, gate green, reviews addressed → human merges
it reports `no change` across three RFCs, delete it and rely on the ledger's
mandatory reasoned exclusion list alone.

## Opinions

The opinions the loop runs on are stated once, in
`docs/loopwright/principles.md`, and this file does not restate them. Three
of them bear directly on the mechanics above:

| Opinion | The question it settles |
|---|---|
| P1 | Where loop state lives |
| P2 | What counts as "done" |
| P8 | Who may perform an irreversible action, merge included |

## Pieces

| Piece | Where |
Expand Down
194 changes: 194 additions & 0 deletions docs/loopwright/principles.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
# Principles

## Context

The destination is near-autonomy, done properly rather than for hype: an
agent takes an issue to a merged change with a human stepping in only where
a human is genuinely needed. The opinions in this document are the
preconditions of that destination, not constraints on it. An agent that
merges on its own is only acceptable if "done" is a mechanical verdict that
cannot be bought. The gate as sole authority, the integrity signals and the
ratchet are what make that verdict mechanical.

This document is descriptive. No skill consults it, no gate checks it, and
no RFC is required to carry a classification line. Its job is to name the
boundary between what is fixed and what is swappable, so the boundary stops
being re-argued case by case.

An opinion is **core** when the rule has no knob. That is a statement about
the rule only. It does not say the rule is frozen, and it does not say the
action the rule governs has no knob: an action may be configurable when the
rule itself provides for it, as P8 does.

How far autonomy may eventually reach is deliberately not declared here.
Declaring a limit would turn a precondition into a ceiling. Nothing in this
document may foreclose an alert-driven investigate, fix, deploy sequence,
and the limit is a decision to revisit only if an actual problem appears,
not a gap to fill pre-emptively.

For how work flows from an issue to a merged change, see
`docs/loopwright/loop-harness.md`. For how a verdict is reached, see
`docs/loopwright/quality-gate.md`.

## Core opinions

- **P1 — Flow.** Work flows RFC issue, then task sub-issues, then PRs. State
lives in artifacts, never in session memory. The trigger is detail; the
flow is core.
- `rejects:` a loop that keeps task state in session memory instead of a
committed artifact, so a crashed session loses the work.
- `enforced by:` `.claude/skills/execute-issue/SKILL.md`
- **P2 — Done.** "Done" is determined by configured checks, never by the
judgement of whoever merges. A merge, autonomous or not, requires every
configured check green for the current diff and review findings resolved.
- `rejects:` a merge, autonomous or human, over a red configured check or
an unresolved review finding.
- `enforced by:` `.loopwright/scripts/quality-gate.mjs`,
`.claude/skills/babysit-pr/SKILL.md`
- **P3 — Ratchet.** Quality is a ratchet against a committed baseline, with
per-file debt grandfathered. The baseline is measured state:
machine-written, engine-schema'd, host-committed, never hand-edited, and
never regenerated to hide a regression. It is not the policy file P7 names.
- `rejects:` regenerating `.loopwright/baseline.json` to make a regression
disappear.
- `enforced by:` `.loopwright/scripts/lib/evaluate.mjs`
- **P4 — Integrity.** Green earned versus green bought — the integrity
signals — is a first-class concern, not an add-on.
- `rejects:` a gate that scores correctness and coverage but leaves the
integrity metrics out, so a suite made green with `.only` and empty
catches still passes.
- `enforced by:` `.loopwright/scripts/lib/collect-metrics.mjs`
- **P5 — Failure blocks.** Infrastructure failure blocks. Disabling a
configured check is itself a violation.
- `rejects:` switching a configured collector off, or letting a collector
that failed to run read as "no data" and merely warn.
- `enforced by:` `.loopwright/scripts/lib/evaluate.mjs`
- **P6 — Mediation.** Agents communicate and coordinate through a mediator,
never peer to peer.
- `rejects:` agents that message each other directly instead of through
the mediator that dispatched them.
- `enforced by:`
- **P7 — Policy.** All loopwright policy lives in one versioned file under
`.loopwright/` in the host repo, and the engine's defaults are frozen.
Changing what the gate demands is a PR in the host repo.
- `rejects:` a second policy file, an environment variable that changes a
verdict, or a loopwright update that moves a host's verdict without a
breaking release.
- `enforced by:` `.loopwright/scripts/lib/paths.mjs`
- **P8 — Irreversibles.** Irreversible actions require a human by default.
Automating one is an explicit opt-in in the host's config file.
- `rejects:` a harness that merges, tags, publishes or deploys without an
explicit opt-in entry in the host's config file.
- `enforced by:` `.claude/skills/babysit-pr/SKILL.md`
- **P9 — Test first.** Strict TDD: the test comes before the implementation.
- `rejects:` a step that writes the implementation first and backfills a
test that was never seen to fail.
- `enforced by:` `.claude/agents/coder.md`
- **P10 — Fresh review.** Fresh-context review: every diff is reviewed by an
agent that did not write it.
- `rejects:` the agent that wrote a diff reviewing its own diff.
- `enforced by:` `.claude/agents/reviewer.md`
- **P11 — Vertical slices.** A unit of work is a vertical slice, never a
horizontal layer.
- `rejects:` a unit of work that delivers a horizontal layer, such as
"define all the types", instead of a slice that ships on its own.
- `enforced by:` `.claude/agents/sub-issue-reviewer.md`
- **P12 — Vocabulary.** The metric vocabulary and its semantics are core and
closed. Connectors measure and never invent a key: per metric, a connector
reports a value for a core-owned key, or `unconfigured` (warns forever,
never blocks on its own), or `failed` (always blocks).
- `rejects:` a connector that reports a metric key core does not define.
- `enforced by:` `.loopwright/scripts/lib/collect-metrics.mjs`

**The P2/P8 division of labour.** P2 decides what "done" is: every
configured check green for the current diff and review findings resolved.
P8 decides who may act on an irreversible action. Nothing may silently
remove the human from an irreversible action; who merges is a default, and
a configurable one. Which CI step may fail the build is mechanics and lives
in `docs/loopwright/quality-gate.md`.

**The irreversibles.** Enumerated today: merge, publishing a tag or
release, and deploy. The list is not exhaustive. Everything else the
harness automates is reversible and needs no opt-in: a closed issue
reopens, `gh pr ready` can be undone, and comments can be edited. A version
bump is not irreversible: it lives in a file inside the PR, and the human
sees it in the same act as the merge.

**The baseline is measured state.** `.loopwright/baseline.json` belongs to
P3. What separates it from the policy file is hand-editing: the policy file is
meant to be edited by hand, and the baseline never is. The host commits the
baseline and leaves its content to the engine.

**An artifact exists before production.** An issue and a PR exist before
anything reaches production, because P1 requires the artifact. Without it
there is nowhere for a diagnosis to live, and a fix deployed without a
trail is green without evidence.

**An empty enforcement pointer is a finding.** An entry whose
`enforced by:` line is empty names a gap. The gap is closed by building the
enforcement, never by writing a pointer to a file that does not enforce the
opinion.

## Detail territory

**Defaults are opinions too.** Every territory ships with one recommended
default, wired out of the box, the way framework starters ship `eslint`. A
default is never a requirement: there is always a documented way to swap it.
The one exception to the swap rule is T6, the engine's implementation
language, which has no swap route.

| id | Territory | Seam | Default |
|---|---|---|---|
| T1 | Host language/ecosystem and the tools that measure it | `connector` | `js` |
| T2 | Stack detection and toolchain installation | `toolchain provider` | `mise`, never required |
| T3 | Where and how agents execute | `runtime` | local session |
| T4 | Thresholds and tolerances | — | already config-driven |
| T5 | Agent host | `agent-host` | Claude Code |
| T6 | Engine implementation language | — | Node |
| T7 | Release tool | `release` | `changesets`, internal, host-first |
| T8 | How work enters the pipeline | `intake` | hand-written RFC |

A default is the recommended choice for that territory. Where the territory is
built, the default names what runs today. Where it has no implementation yet,
the default names what it will be built with.

This document defines no contract for these territories. Each belongs to
its own RFC. T5 is the one exception.

T6 is independent of T1: CI written in a different language than the project
is ordinary, so a Go host needing Node in CI is not coupling.

**T5 is a contract of exactly four capabilities** the loop requires of its
agent host:

1. Dispatch a subagent with fresh context and an explicit payload.
2. Restrict tools per role.
3. Choose a model tier per task.
4. An external reviewer that can write on the PR.

Anything the loop needs beyond those four is leakage: it either enters the
contract as a core change, or it is a bug.

Capability 2 is load-bearing for P10. A reviewer is read-only by being handed
no write tools, not by being told to behave.

The engine has zero coupling here: nothing under `.loopwright/scripts/`
depends on Claude, agents, skills or sessions.

## Classification rule

If a change alters what counts as done, or how work flows, it is core. It
must be argued against the opinions above.

If a change alters how a fact is measured, or where a step runs, it belongs
behind a contract, with a default implementation.

## Amendments

Changing a core opinion is an explicit amendment: a dated entry naming the
RFC that changed it.

Ids are stable. Amendments never renumber. A withdrawn opinion stays in
place, and keeps its id, in the form `P4 (withdrawn, RFC #N)`.

There are no amendments yet.
Loading
Loading