From f981739990ed26902c45019d8bd6b5ba9deda41e Mon Sep 17 00:00:00 2001 From: Samuel Denani Date: Mon, 5 Oct 2026 19:55:45 -0300 Subject: [PATCH 01/13] docs: add spec for #11 Co-Authored-By: Claude Opus 5 --- docs/specs/issue-11.md | 607 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 607 insertions(+) create mode 100644 docs/specs/issue-11.md diff --git a/docs/specs/issue-11.md b/docs/specs/issue-11.md new file mode 100644 index 0000000..313582e --- /dev/null +++ b/docs/specs/issue-11.md @@ -0,0 +1,607 @@ +# Issue #11 — Write docs/loopwright/principles.md + +RFC: #5 · Branch: task/11-principles-doc · Base: feat/rfc-5-core-principles + +## Spec + +Create one new file, `docs/loopwright/principles.md`, holding the descriptive +statement of loopwright's core opinions (P1–P12) and its detail territory +(T1–T8), plus the classification rule and the amendment mechanism. The doc is +the artifact RFC #5 exists to produce: #6, #7 and #8 already cite it textually, +and #12 and #13 are blocked on it existing. It gates nothing, no skill consults +it, and no RFC is required to carry a classification line — its job is to name +the boundary so it stops being re-argued. + +The source of truth for content is RFC #5's body (`gh issue view 5`): the +**Decision** section carries the P1–P12 table and the T1–T8 table, and the +**Detail territory** section carries T5's four capabilities and the T6/T7 notes. +Two things are **not** in those tables and must be authored or lifted from +prose: the twelve `rejects:` lines (the RFC mandates them but supplies none — +seeds are in Step 2 below), and the normative residue that lives only in the +RFC's *"Why the contested branches settled where they did"* prose. The rule for +that prose, stated by #11: a rule a reader must obey travels into the doc; the +story of how it was decided stays in the issue. + +This is documentation only. Nothing under `.loopwright/scripts/`, `.claude/` or +`.github/` changes, and no test exists or can exist for a markdown file — see +**How steps are verified** before Step 1. + +**The path and the id scheme are already promised.** RFC #5's close-out act has +been performed: #6, #7, #8, #9 and #10 each end with an identical `## Boundary +document` footer naming `docs/loopwright/principles.md` (task #11) and stating +that core opinions are cited as `P` and detail territory as `T`. The file +must land at exactly that path with exactly that id scheme, or five issues point +at nothing. #9's footer additionally carries a supersession note citing `P8` by +id — which is why the doc itself needs no citation of #9 (Step 3). + +### Acceptance criteria + +- [ ] `docs/loopwright/principles.md` exists and opens with the Context that + states near-autonomy as the destination and the core opinions as its + preconditions, not its constraints. +- [ ] P1–P12 are present as a list with stable ids, each carrying three things: + the assertion, a `rejects:` line naming one concrete change it refuses, + and an enforcement pointer that is a file name with no line number (left + empty where nothing enforces it). +- [ ] T1–T8 are present, each with its territory and its current default. Six + carry a named seam; T4 and T6 carry none. No contract is specified for + T1, T2, T3, T7 or T8, with T5 the one exception, whose four required + capabilities are enumerated in full. +- [ ] The classification rule and an `Amendments` section are present; the + amendment rule states that ids are stable, amendments never renumber, and + a withdrawn opinion stays as `P4 (withdrawn, RFC #N)`. +- [ ] P7 names the policy file only. `baseline.json` is described as measured + state belonging to P3, never as policy. +- [ ] P12's assertion carries the three-valued connector contract — a value for + a core-owned key, `unconfigured`, `failed` — with each state's verdict + stated (`unconfigured` warns and never blocks; `failed` always blocks). +- [ ] Read against #6, #7, #8, #9 and #10, the doc contradicts none of them + except the #9 version-bump contradiction, which the RFC states is + deliberate. +- [ ] Style matches the two siblings in `docs/loopwright/` (see **Style + contract**), and the quality gate is green. + +### Out of scope + +- Any change to `.loopwright/scripts/`, `.loopwright/tests/`, `.claude/` or + `.github/`. RFC #5's non-goals forbid an engine or workflow change. +- Any change to `docs/loopwright/loop-harness.md` (that is #12) or to + `README.md`, `CLAUDE.md`, `.loopwright/claude-md-section.md`, + `package.json` (that is #13). Do not touch them even to fix a reference. +- A **"Known violations" section**, and any mention of the known P5 divergence + at `.loopwright/scripts/quality-gate.mjs:97-99`. RFC #5 considered recording + it and rejected it explicitly. +- Defining any contract for T1, T2, T3, T4, T6, T7 or T8. T5's four + capabilities are the single exception the RFC resolves. +- **History**: which alternative the grill rejected, what the RFC said before + refinement, which RFC a decision contradicts, the names #9/#6/#8 as sources + of a decision. The doc carries rules, not provenance. +- Mermaid or any diagram. The four boundary diagrams on #5 are scaffolding; the + doc names seams and draws nothing. +- Appending the pointer to this doc into the bodies of #6–#10. RFC #5 makes + that a close-out act on the RFC, not part of this task. +- Naming the policy file's eventual filename (`config.yml` vs `settings.yml`) — + that belongs to #8. P7 asserts "one file, under `.loopwright/`" only. + +## Context the coder needs + +### Style contract (measured from the siblings, not guessed) + +Both siblings were measured; these are facts, not preferences. + +| Rule | Evidence | +|---|---| +| Single `#` title, subject in sentence case | `loop-harness.md:1` `# Loop harness`, `quality-gate.md:1` `# Quality gate` | +| Short sentence-case `##` section names | `Flow`, `Rules of the loop`, `Pieces`, `Baselines`, `What is measured`, `Tuning` | +| No front matter, no date, no emoji, no author | neither sibling has any | +| Impersonal declarative voice, no "we"/"you should" | throughout; `quality-gate.md` uses "you" only in the reader-advice sense at `:53`, `:178` | +| Tables for anything enumerable | `loop-harness.md:81-88`, `quality-gate.md:82-87`, `:157-165` | +| Bold lead-in term in list items | `loop-harness.md:46` `- **Base branch**: …`, `quality-gate.md:144` `**Coverage** — …` | +| Every path, filename, tool and metric name backticked | throughout both | +| Prose wraps at ~78 chars | max prose line is 80 chars (`quality-gate.md:59`, `:101`, `:111`); 78 is the target | +| Table rows and fenced code blocks are exempt from the wrap | table rows reach 98 chars (`quality-gate.md:163`) | +| Cross-reference the sibling docs by path | `loop-harness.md:4` cites `docs/loopwright/quality-gate.md` | + +Count characters, not bytes: an em dash is three bytes, so `awk 'length>78'` +over-reports. Use the Python check given in each step's **Verify**. + +### Section spine (decided here so the coder does not dither) + +``` +# Principles +## Context +## Core opinions +## Detail territory +## Classification rule +## Amendments +``` + +`## Context` is the **first** section with no lead paragraph before it, because +AC 1 requires the doc to open with the Context. The sibling convention of an +opening cross-reference is satisfied inside Context, which cites +`docs/loopwright/loop-harness.md` and `docs/loopwright/quality-gate.md`. The +rationale prose for the opinions sits as paragraphs **below** the P1–P12 list, +inside `## Core opinions`, per the RFC's Form rule ("Rationale goes in prose +below the list") — no sixth section for it. + +### Entry form for P1–P12 (fixed, so AC 2 is greppable) + +```markdown +- **P1 — Flow.** + - `rejects:` + - `enforced by:` `` +``` + +The `rejects:` and `enforced by:` tokens are literal and backticked, on their +own nested bullet, exactly twelve times each. Where nothing enforces an +opinion, the line is `- \`enforced by:\`` with nothing after it — the gap stays +visible, because RFC #5 states an empty pointer is itself a finding. Pointers +are file paths with **no line number**. + +### Enforcement pointers (verified by reading each file — do not invent) + +| id | `enforced by:` | What was read | +|---|---|---| +| P1 | `.claude/skills/execute-issue/SKILL.md` | spec committed at `:140`, "Session memory does not" at `:123` | +| P2 | `.loopwright/scripts/quality-gate.mjs`, `.claude/skills/babysit-pr/SKILL.md` | the gate decides the checks half; babysit-pr `:48-56` enforces the review half | +| P3 | `.loopwright/scripts/lib/evaluate.mjs` | the ratchet, `evaluateMetric` at `:87`, grandfathering at `:183-219` | +| P4 | `.loopwright/scripts/lib/collect-metrics.mjs` | `INTEGRITY_METRICS` at `:290-297` | +| P5 | `.loopwright/scripts/lib/evaluate.mjs` | `failed` → BLOCK at `:112-113`; `collectorRegressions` at `:150-162` ("disabling a tool is not a way to pass the gate") | +| P6 | *(empty)* | nothing in `.loopwright/scripts/` or `.claude/` enforces mediation; grep for "mediat"/"peer to peer" returns nothing | +| P7 | `.loopwright/scripts/lib/paths.mjs` | `CONFIG_PATH` is a single constant at `:11`, and `quality-gate.mjs:49` / `run-report.mjs:39` read only it | +| P8 | `.claude/skills/babysit-pr/SKILL.md` | `:56` "**Merging is the user's decision — never merge.**" | +| P9 | `.claude/agents/coder.md` | `:12` "## TDD loop (mandatory for plan steps)", `:15`, `:33` | +| P10 | `.claude/agents/reviewer.md` | frontmatter `:4` `tools: Read, Grep, Glob, Bash` — no write tool | +| P11 | `.claude/agents/sub-issue-reviewer.md` | `:27-28` rejects a horizontal layer | +| P12 | `.loopwright/scripts/lib/collect-metrics.mjs` | `COLLECTOR_METRICS` at `:76` is the closed id map | + +Tie-break used: name the file that would **reject** a violation, not the file +that merely states the rule. That is why P11 points at +`sub-issue-reviewer.md` and not `grill-rfc/SKILL.md`, which also carries the +rule at `:273-274`. P2 is the one entry with two paths, because the opinion has +two clauses with two different enforcers; keep both, and see **Open questions**. + +Line numbers in the table above are for the coder's verification only. **None +of them appears in the doc.** + +### How steps are verified + +There is no test, because the deliverable is a markdown file and nothing +executes it. Inventing a vitest case that asserts on the text of a doc would be +a test with no subject, and the repo's collectors cannot see the file anyway: +`.loopwright/config.json` sets `sources.roots` to `.loopwright/scripts` and +`.loopwright/tests` with `extensions: ['.mjs']`, so no metric moves on a +markdown-only diff. + +Each step therefore substitutes a **check first**: a command that fails before +the step's change and passes after, plus a readthrough against the one +acceptance criterion the step owns. Run the check before writing, see it fail, +then make it pass. That is the same discipline as TDD with the only executable +surface available. + +### Commit hygiene — read before the first commit + +`coverage/` and `reports/` exist untracked in the repo root and are **not** in +`.gitignore` (only `.loopwright/reports/` is). They hold 28 generated +artifacts. Every commit in this plan must add explicit paths: + +```bash +git add docs/loopwright/principles.md # or docs/specs/issue-11.md +``` + +Never `git add -A`, never `git add .`, never `git add docs`. Verify with +`git status --short` after each commit that `?? coverage/` and `?? reports/` +are still untracked. + +## Implementation plan + +Steps 1–5 each add one complete section and leave the repo green by +construction (markdown is outside every collector's scope, proven above). Step +6 is the cross-RFC and style pass. Step 7 runs the gate once, because an +unconfigured-to-failed collector transition blocks for reasons unrelated to the +diff and must be caught before the PR opens. + +### Step 1: The file, its spine, and the Context (AC 1) + +- **Check first**: the file does not exist. + ```bash + test -f docs/loopwright/principles.md && echo PRESENT || echo ABSENT + ``` + Expected before: `ABSENT`. +- **Then**: create `docs/loopwright/principles.md` with the title `# Principles` + and the five `##` headings from **Section spine** in order, each followed for + now only by the Context body. Context must carry, in impersonal prose: + - Near-autonomy, done properly rather than for hype, as the **destination**, + and the opinions below as its **preconditions, not its constraints** — an + agent that merges on its own is only acceptable if "done" is a mechanical + verdict that cannot be bought. + - That the doc is **descriptive**: no skill consults it, no gate checks it, + no RFC carries a classification line. + - The definition of **core**: the rule has no knob — not that the rule is + frozen, and not that the action it governs has no knob (an action may be + configurable when the rule itself provides for it, as P8 does). + - That the limit of autonomy is deliberately not declared here. + - The cross-references: `docs/loopwright/loop-harness.md` for how work flows + and `docs/loopwright/quality-gate.md` for how a verdict is reached. +- **Files**: `docs/loopwright/principles.md` +- **Verify**: + ```bash + test -f docs/loopwright/principles.md && \ + grep -n '^#\{1,2\} ' docs/loopwright/principles.md + ``` + Exactly one `# ` line, then `## Context`, `## Core opinions`, + `## Detail territory`, `## Classification rule`, `## Amendments` in that + order. Then read the Context against AC 1: it must state near-autonomy as the + destination and the opinions as preconditions. A Context that only says "this + doc lists principles" fails the step. +- **Commit**: `docs: add principles doc skeleton and context` + +### Step 2: P1–P12 as a list with ids, `rejects:` and pointers (AC 2, AC 5, AC 6) + +- **Check first**: + ```bash + grep -c '`rejects:`' docs/loopwright/principles.md + grep -c '`enforced by:`' docs/loopwright/principles.md + ``` + Expected before: `0` and `0`. After: `12` and `12`. +- **Then**: under `## Core opinions`, write the twelve entries in the fixed + **Entry form**. Take each assertion from RFC #5's Decision table + (`gh issue view 5`) — its wording is authoritative; compress only to fit the + wrap, never change the claim. Three entries carry extra required content: + - **P7** names the policy file and *only* the policy file: all loopwright + policy lives in one versioned file under `.loopwright/` in the host repo, + the engine's defaults are frozen, and changing what the gate demands is a + PR in the host repo. P7 must not mention `baseline.json` and must not name + a filename (#8 owns the name). + - **P3** states that the baseline is **measured state**: machine-written, + engine-schema'd, host-committed, never hand-edited, and never regenerated + to hide a regression. Either P3's assertion or the prose in Step 3 must say + it is not the policy file P7 names; putting it in P3's assertion is the + safer reading of AC 5. + - **P12**'s assertion carries the full three-valued connector contract: a + connector reports, per metric, either a value for a core-owned key, or + `unconfigured`, or `failed`. `unconfigured` warns forever and never blocks; + `failed` always blocks. "Connectors measure, they never invent a key" alone + fails AC 6. + - Use the `enforced by:` column from the table above verbatim. +- **Seed `rejects:` lines** — the RFC mandates the line and supplies none, so + these are authored. Refine the wording to fit the wrap; do not weaken one + into a slogan. Each must name a change somebody could actually propose: + - P1: a loop that keeps task state in session memory instead of a committed + artifact, so a crashed session loses the work. (Keep this narrow: #7 permits + a local mailbox cache in front of GitHub, which stays the source of truth. + A line broad enough to forbid a cache would contradict #7.) + - P2: a merge — autonomous or human — over a red configured check or an + unresolved review finding. + - P3: regenerating `.loopwright/baseline.json` to make a regression + disappear. + - P4: a gate that scores correctness and coverage but leaves the integrity + metrics unconfigured, so a suite made green with `.only` and empty catches + still passes. + - P5: switching a configured collector off, or letting a collector that + failed to run read as "no data" and merely warn. (P5 may assert that this + **blocks**: `collectorRegressions` returns `status: STATUS.BLOCK` at + `.loopwright/scripts/lib/evaluate.mjs:158`. #8's open question 3 describes + today's behaviour as a PR-comment flag, which understates the code.) + - P6: agents that message each other directly instead of through the + mediator that dispatched them. + - P7: 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. + - P8: a harness that merges, tags, publishes or deploys without an explicit + opt-in entry in the host's config file. + - P9: a step that writes the implementation first and backfills a test that + was never seen to fail. + - P10: the agent that wrote a diff reviewing its own diff. + - P11: a unit of work that delivers a horizontal layer — "define all the + types" — instead of a slice that ships on its own. + - P12: a connector that reports a metric key core does not define. +- **Files**: `docs/loopwright/principles.md` +- **Verify**: + ```bash + grep -c '^- \*\*P' docs/loopwright/principles.md # 12 + grep -c '`rejects:`' docs/loopwright/principles.md # 12 + grep -c '`enforced by:`' docs/loopwright/principles.md # 12 + grep -n 'baseline' docs/loopwright/principles.md # none inside P7 + grep -n 'unconfigured\|failed' docs/loopwright/principles.md # both inside P12 + grep -nE '\.(mjs|md|json):[0-9]' docs/loopwright/principles.md # must be empty + ``` + The last check is the AC 2 trap: a pointer with a line number fails. Then + read P7, P3 and P12 against AC 5 and AC 6. +- **Commit**: `docs: add the twelve core opinions with rejects lines` + +### Step 3: The rationale prose below the list (AC 5, AC 6 support) + +- **Check first**: + ```bash + grep -c 'division of labour\|decides' docs/loopwright/principles.md + ``` + Expected before: `0` for the P2/P8 paragraph's lead term (adjust the grep to + the lead-in actually chosen; the point is the paragraphs are absent). +- **Then**: below the twelve-entry list, still inside `## Core opinions`, write + the short rationale paragraphs with bold lead-ins. Exactly these, and nothing + that is history: + - **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, deploy — the list is not exhaustive. Everything else the harness + automates is reversible and needs no opt-in: closing an issue reopens, + `gh pr ready` de-promotes, comments edit. 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. **Do not** name #9 or say that this contradicts a + published RFC — that is the history that stays in the issue. + - **The baseline is measured state.** Reinforce, in one or two sentences, + that `.loopwright/baseline.json` belongs to P3 and is not the policy file + P7 names. + - **Defaults are opinions too.** One recommended default per territory, + wired out of the box, the way framework starters ship `eslint`; a default + is never a requirement, and there is always a documented way to swap it. + (This may instead open `## Detail territory` in Step 4 — pick one place, + not both.) + - **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**, not a completed entry. +- **Files**: `docs/loopwright/principles.md` +- **Verify**: readthrough. The prose contains no sentence naming an issue + number as the source of a decision, no "originally", "was rejected", + "before the grill", or "contradicts". + ```bash + grep -niE 'rejected|originally|before the grill|contradict|alternative' \ + docs/loopwright/principles.md # expect no hits + ``` +- **Commit**: `docs: add the rationale prose under the core opinions` + +### Step 4: Detail territory T1–T8, with T5's four capabilities (AC 3) + +- **Check first**: + ```bash + grep -c '^| T[1-8] ' docs/loopwright/principles.md + ``` + Expected before: `0`. After: `8`. +- **Then**: under `## Detail territory`, reproduce the RFC's table — a table, + because it is enumerable — with the columns `id`, `Territory`, `Seam`, + `Default today`, taking all eight rows from RFC #5's Decision section. Then, + below it: + - **T4 and T6 have `—` in the Seam column.** The RFC omits them + deliberately. Inventing a seam name for either is a defect. + - **Strip the issue numbers** the RFC's table carries in its default column + (`js (#6)`, `mise, never required (#6)`, `container per #7`, + `changesets, internal, host-first`) — the default travels, the citation + does not. Keep the defaults themselves exactly. + - One line stating this doc defines **no** contract for these territories; + each belongs to its own RFC. + - **T5 is the one exception**, and its four capabilities are enumerated in + full, as a list: dispatch a subagent with fresh context and an explicit + payload; restrict tools per role; choose a model tier per task; an external + reviewer that can write on the PR. Add that anything the loop needs beyond + those four is leakage — it either enters the contract as a core change or + it is a bug; that capability 2 is load-bearing for P10, because a reviewer + is read-only by being handed no write tools, not by being told to behave; + and that the engine has zero coupling here — nothing under + `.loopwright/scripts/` references Claude, agents, skills or sessions. + - **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. One + line. Do not turn it into a contract. + - **T7: name the seam and the default, and stop there.** Write the seam + (`release`) and the default (changesets, internal to loopwright, + host-first — if the host already uses it, loopwright uses the host's). + **Do not** write the RFC's further claim that T7 therefore works on a + non-JS host because the engine is already Node. #9's own body says the + opposite — that `@changesets/cli` assumes a `package.json` and "conflicts + with the connectors RFC for non-JS hosts", and that the release tool "should + be pluggable" (knope elsewhere). Omitting the claim satisfies AC 3 ("no + contract is specified for T7") and avoids a second contradiction with #9 + that AC 7 does not sanction. See **Open questions**. + - **Do not add a T9.** #10's territory (first-time setup / installer UX) is + not in T1–T8, and AC 3 fixes the list at eight entries. The gap is recorded + in **Open questions**, not filled here. +- **Files**: `docs/loopwright/principles.md` +- **Verify**: + ```bash + grep -c '^| T[1-8] ' docs/loopwright/principles.md # 8 + grep -nE '^\| T(4|6) ' docs/loopwright/principles.md # Seam column is — + grep -n '#[6-9]\|#10' docs/loopwright/principles.md # no issue refs in the table + ``` + Then count the T5 capabilities: exactly four, no fifth. Read AC 3 line by + line against the section. +- **Commit**: `docs: add the detail territory and the agent-host contract` + +### Step 5: Classification rule and Amendments (AC 4) + +- **Check first**: + ```bash + grep -c 'withdrawn' docs/loopwright/principles.md + ``` + Expected before: `0`. After: at least `1`. +- **Then**: + - `## Classification rule`: if a change alters what counts as done, or how + work flows, it is core and must be argued against the opinions above. If it + 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 and amendments never + renumber. A withdrawn opinion stays in place as `P4 (withdrawn, RFC #N)`, + written exactly in that form as the example. State that there are no + amendments yet; do not fabricate an entry, and do not add a date anywhere + else in the doc. +- **Files**: `docs/loopwright/principles.md` +- **Verify**: + ```bash + grep -n 'P4 (withdrawn, RFC #N)' docs/loopwright/principles.md + grep -n '^## Classification rule' docs/loopwright/principles.md + grep -n '^## Amendments' docs/loopwright/principles.md + ``` + All three must hit. Read AC 4: stable ids, never renumber, and the withdrawn + form must all be stated, not implied. +- **Commit**: `docs: add the classification rule and the amendment mechanism` + +### Step 6: Cross-RFC consistency and style pass (AC 7, AC 8) + +- **Check first**: run the wrap check; it will report the prose lines the + drafting left long. + Save this as `/tmp/wrapcheck.py` and run `python3 /tmp/wrapcheck.py` — do not + inline it, the fence marker does not survive shell quoting: + + ```python + FENCE = chr(96) * 3 + path = 'docs/loopwright/principles.md' + inside = False + for n, line in enumerate(open(path, encoding='utf-8').read().split('\n'), 1): + if line.startswith(FENCE): + inside = not inside + continue + if inside or line.lstrip().startswith('|'): + continue + if len(line) > 80: + print(n, len(line), line[:50]) + ``` + Expected after the pass: no output. Table rows and fenced blocks are exempt + by the measured sibling convention. +- **Then**: + 1. **Confirm the four in-body citations land.** These were read out of the + five bodies; each one is a phrase an RFC already uses, so the doc has to + make it true: + + | Citation, verbatim from the RFC | What the doc must satisfy it with | + |---|---| + | #7: "The runtime is a detail behind a contract, **per the principles RFC**" | T3 is a territory with a named seam (`runtime`) and a default | + | #7: peer-to-peer messaging "**conflicts with the mediated principle**" | P6 is nameable as the mediated principle and is strong enough to reject peer-to-peer | + | #7: "consistent with the principle that **state never lives in session memory**" | P1's second clause says exactly that | + | #8: "the section boundaries mirror the principles RFC: **each feature is a detail with a default, and its section is where you swap it**" | every T-row carries a default, and "there is always a documented way to swap it" is present | + + #6 carries the defaults rule verbatim but uncited ("mise is the default + provider, the way eslint is the default linter that framework starters + ship with"), so "Defaults are opinions too" must be recognizable as the + same rule. #9 and #10 cite nothing in-body beyond their footers. + 2. **Check the two territory namings against the RFCs that own them.** #7's + T3 default is plain Docker and its non-goal is "Keeping git worktrees as a + supported isolation level" — so the doc must not describe a worktree as the + current default. #8 and #9 both write the config section as `changesets:`, + while #5's T7 row names the seam `release`; if the doc writes both, it must + be unambiguous that `release` is the seam and changesets is today's default + — never that `release` is a config key. + 3. **Read the doc against each of #6–#10 for any further contradiction.** The + RFCs are the facts; the doc adjusts to them. The one permitted + contradiction is #9 on who decides the version bump — leave it. Two known + near-misses that are **not** doc bugs and need no edit: #6's connector + contract is two-valued (a value or `unconfigured`) while P12 is + three-valued — `failed` is real in the engine + (`.loopwright/scripts/lib/evaluate.mjs:112-113`), so P12 adds a state + rather than contradicting #6; and P7's frozen defaults answer a question #8 + leaves open rather than restating it. Anything else found: fix the doc, + never the RFC, and report it. + 4. Fix the wrap, then sweep the style contract: no emoji, no front matter, no + date, no "we"/"I", every path and tool name backticked, bold lead-ins on + list items, section names short and sentence-case. +- **Files**: `docs/loopwright/principles.md` +- **Verify**: + ```bash + python3 -c "print(open('docs/loopwright/principles.md',encoding='utf-8').read().count('---'))" + grep -nE '\b(we|I|our)\b' docs/loopwright/principles.md # expect no hits + grep -n '^## ' docs/loopwright/principles.md # five sections, unchanged + ``` + Plus the wrap check above returning nothing, and a written note of what each + of #6–#10 was checked against. +- **Commit**: `docs: reconcile the principles doc with RFCs #6-#10` + +### Step 7: Gate run and final readthrough (all ACs) + +- **Check first**: the gate has not been run against this branch. + ```bash + node .loopwright/scripts/run-report.mjs --all && \ + node .loopwright/scripts/quality-gate.mjs + ``` +- **Then**: nothing, if the verdict is green. A markdown-only diff cannot move + a metric (`sources.roots` is `.loopwright/scripts` and `.loopwright/tests`, + `extensions: ['.mjs']`), so any blocker is pre-existing or environmental. + If it is red, read `.loopwright/reports/quality-gate.json` and report the + verdict to the orchestrator — do **not** touch `.loopwright/baseline.json`, + `.loopwright/config.json` or any engine file to make it green. Both are out + of scope for this task. +- **Files**: none. `.loopwright/reports/` is gitignored; the untracked + `coverage/` and `reports/` directories the run refreshes must stay + untracked. +- **Verify**: + ```bash + node .loopwright/scripts/quality-gate.mjs ; echo "exit=$?" + git status --short # only docs/ paths; coverage/ and reports/ still ?? + ``` + Exit `0`. Then a final readthrough of the whole file against all eight + acceptance criteria, in order, ticking each. +- **Commit**: nothing to commit unless the readthrough found a fix, in which + case `docs: ...` with `git add docs/loopwright/principles.md` only. + +## Risks and open questions + +- **The twelve `rejects:` lines are authored, not lifted.** RFC #5 mandates the + line and its Decision table supplies none. Step 2's seeds are derived from + the RFC's own prose, but the wording is the coder's, and the RFC's first risk + ("principles written too broadly stop deciding anything") lands here. The + reviewer should read each line against "could somebody actually propose + this change?". +- **P2 carries two enforcement pointers.** AC 2 says "a file name", singular, + but P2 has two clauses with two enforcers: the gate decides the checks half, + `.claude/skills/babysit-pr/SKILL.md:48-56` the review half. Dropping either + misdescribes the opinion. Flagged rather than resolved. +- **P7's "frozen defaults" is not enforced today.** + `.loopwright/scripts/detect-stack.mjs:104-121` *copies* + `config.default.json` into `config.json` once and refuses to overwrite — it + is not an overlay, so there are no live defaults to freeze. #8 owns that + change. P7 describes the decided state; its pointer + (`.loopwright/scripts/lib/paths.mjs`) honestly covers only the "one file" + clause. +- **P8 is the one non-descriptive line in a descriptive doc.** It describes the + decided state, and the opt-in knob it assumes does not exist. The doc should + not hedge it into vagueness, and should not claim the knob exists. +- **The version-bump consequence is resolved, not open.** AC 7 implies the doc + does contradict #9, so the consequence travels; the issue's rule means the + words "#9" and "contradicts" do not. Step 3 resolves it as "a version bump is + not an irreversible action", with no citation — and that is safe because #9's + own `## Boundary document` footer already carries the supersession note, + citing `P8` by id and giving the same reasoning ("a version bump is reversible + inside the PR — the human still sees it in the same act as the merge"). The + cross-reference lives on the issue, where the RFC put it. +- **T7 is the one place the RFC and #9 genuinely conflict, and the plan avoids + it rather than deciding it.** RFC #5's Detail territory prose asserts "T7 + works on a non-JS host because of T6 … changesets can be internal to + loopwright and still serve a Go host". #9's body says the opposite: that + `@changesets/cli` assumes a `package.json`, "conflicts with the connectors + RFC for non-JS hosts", and that "the *release tool* that consumes it should be + pluggable (changesets on JS, a single-binary tool such as knope elsewhere)". + Under #5's own one-way consistency rule the RFCs are the facts, and AC 7 + sanctions only the version-bump contradiction — so Step 4 omits the claim and + writes the seam and default only, which AC 3 independently requires. **This is + the one item the orchestrator may want to rule on before Step 4 runs.** +- **P8 versus #9's release workflow — a second P8/#9 tension, not planned + around.** P8's enumerated irreversibles include "publishing a tag or + release"; #9 has a CI workflow that "creates a git tag and GitHub release" + and never mentions an opt-in. It may not be a real conflict — changesets' + usual shape is a release PR a human merges, which satisfies P8 through the + merge — and #9 is not yet grilled. P8's wording comes from the RFC's + authoritative Decision table and must not be weakened to dodge this, so the + plan states P8 as given and records the tension here. +- **`settings.yml` is a misnomer in the RFC, and the plan sidesteps it.** RFC + #5's open question 3 calls #8's filename alternatives "`config.yml` vs + `settings.yml`"; #8's actual open question 2 is "`config.yml` or + `loopwright.yml`". Out of scope already forbids the doc from naming the file + at all, which is why this cannot leak in. +- **A ninth territory is visible but must not be added.** #10's subject — + first-time setup / installer UX — is not in T1–T8, and #10 claims no seam of + its own (it consumes T2's bundled `mise`). AC 3 fixes the list at eight, so + Step 4 forbids a T9. Whether the RFC's territory list is complete is a + question for #5, not for this task. +- **P7's freeze is a new decision presented as a settled one.** RFC #5 says P7 + "settles #8's open question 1"; #8 leaves that question open and leans the + other way for the gate section ("a middle ground: overlay for feature + sections, full copy for `gate:`"), calling it "the decision that matters most + here". P7's assertion is still the RFC's to make — the doc states it without + claiming #8 agreed. +- **`# Principles` as the title** is chosen to match the siblings' pattern + (subject in sentence case, no subtitle). RFC #5's own title is longer; the + file is `principles.md`. From cbf6eb89c626215fdbaabeb817d080f75bc9f086 Mon Sep 17 00:00:00 2001 From: Samuel Denani Date: Mon, 5 Oct 2026 20:11:15 -0300 Subject: [PATCH 02/13] docs: add principles doc skeleton and context Co-Authored-By: Claude Opus 5 --- docs/loopwright/principles.md | 39 +++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 docs/loopwright/principles.md diff --git a/docs/loopwright/principles.md b/docs/loopwright/principles.md new file mode 100644 index 0000000..a29e3a2 --- /dev/null +++ b/docs/loopwright/principles.md @@ -0,0 +1,39 @@ +# 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, and every core opinion exists to keep that verdict +mechanical, measured and out of the agent's reach. + +This document is descriptive. No skill consults it, no gate checks it, and +no RFC carries 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. Everything that has a knob and a +current default is detail territory. + +How far autonomy may eventually reach is deliberately not declared here. +Declaring a limit would turn a precondition into a ceiling, and the limit +is a judgement that belongs to whoever relies on the loop, not to this +document. + +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 + +## Detail territory + +## Classification rule + +## Amendments From 2280f704f2c7552c2ae9e4f654ae9855490b3b9f Mon Sep 17 00:00:00 2001 From: Samuel Denani Date: Mon, 5 Oct 2026 20:43:10 -0300 Subject: [PATCH 03/13] fix: tighten principles Context claims (#11) Co-Authored-By: Claude Opus 5 --- docs/loopwright/principles.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/loopwright/principles.md b/docs/loopwright/principles.md index a29e3a2..8ff9ecc 100644 --- a/docs/loopwright/principles.md +++ b/docs/loopwright/principles.md @@ -7,24 +7,24 @@ 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, and every core opinion exists to keep that verdict -mechanical, measured and out of the agent's reach. +cannot be bought. The gate as sole authority, the integrity signals and the +ratchet are preconditions of autonomy, not constraints on it. This document is descriptive. No skill consults it, no gate checks it, and -no RFC carries 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. +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. Everything that has a knob and a -current default is detail territory. +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, and the limit -is a judgement that belongs to whoever relies on the loop, not to this -document. +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 From 8348fc530985ce4449a5b6cbfb7990ff84dae8d1 Mon Sep 17 00:00:00 2001 From: Samuel Denani Date: Wed, 7 Oct 2026 16:16:29 -0300 Subject: [PATCH 04/13] docs: add the twelve core opinions with rejects lines (#11) Co-Authored-By: Claude Opus 5 --- docs/loopwright/principles.md | 68 +++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/docs/loopwright/principles.md b/docs/loopwright/principles.md index 8ff9ecc..27586ea 100644 --- a/docs/loopwright/principles.md +++ b/docs/loopwright/principles.md @@ -32,6 +32,74 @@ For how work flows from an issue to a merged change, see ## 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), or `failed` (always blocks). + - `rejects:` a connector that reports a metric key core does not define. + - `enforced by:` `.loopwright/scripts/lib/collect-metrics.mjs` + ## Detail territory ## Classification rule From 07547ec62588d33683d98650b0f14371cb77c267 Mon Sep 17 00:00:00 2001 From: Samuel Denani Date: Wed, 7 Oct 2026 16:24:28 -0300 Subject: [PATCH 05/13] docs: add the rationale prose under the core opinions (#11) Co-Authored-By: Claude Opus 5 --- docs/loopwright/principles.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/docs/loopwright/principles.md b/docs/loopwright/principles.md index 27586ea..debfb7e 100644 --- a/docs/loopwright/principles.md +++ b/docs/loopwright/principles.md @@ -100,6 +100,32 @@ For how work flows from an issue to a merged change, see - `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 and is not the policy file P7 names. + +**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 is not a completed entry; it names a gap to +close. + ## Detail territory ## Classification rule From 835971a78eddb455997d2faceb7511f8925bac60 Mon Sep 17 00:00:00 2001 From: Samuel Denani Date: Wed, 7 Oct 2026 16:31:32 -0300 Subject: [PATCH 06/13] docs: add the detail territory and the agent-host contract (#11) Co-Authored-By: Claude Opus 5 --- docs/loopwright/principles.md | 40 +++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/docs/loopwright/principles.md b/docs/loopwright/principles.md index debfb7e..874a786 100644 --- a/docs/loopwright/principles.md +++ b/docs/loopwright/principles.md @@ -128,6 +128,46 @@ close. ## 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. + +Each territory below is a named seam and the default behind it today. + +| id | Territory | Seam | Default today | +|---|---|---|---| +| 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; container | +| 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 | + +This doc 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/` +references Claude, agents, skills or sessions. + ## Classification rule ## Amendments From d6011074014970ed0690e205de0cf6f165496569 Mon Sep 17 00:00:00 2001 From: Samuel Denani Date: Wed, 7 Oct 2026 16:33:01 -0300 Subject: [PATCH 07/13] fix: correct detail territory claims (#11) Co-Authored-By: Claude Opus 5 --- docs/loopwright/principles.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/loopwright/principles.md b/docs/loopwright/principles.md index 874a786..8b84daa 100644 --- a/docs/loopwright/principles.md +++ b/docs/loopwright/principles.md @@ -132,13 +132,11 @@ close. 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. -Each territory below is a named seam and the default behind it today. - | id | Territory | Seam | Default today | |---|---|---|---| | 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; container | +| 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 | @@ -166,7 +164,7 @@ 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/` -references Claude, agents, skills or sessions. +depends on Claude, agents, skills or sessions. ## Classification rule From f38fa5338ccc5316e3ff09735c723578b3cb868c Mon Sep 17 00:00:00 2001 From: Samuel Denani Date: Wed, 7 Oct 2026 16:44:37 -0300 Subject: [PATCH 08/13] fix: rename the territory default column and define a default (#11) Co-Authored-By: Claude Opus 5 --- docs/loopwright/principles.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/loopwright/principles.md b/docs/loopwright/principles.md index 8b84daa..cf65b69 100644 --- a/docs/loopwright/principles.md +++ b/docs/loopwright/principles.md @@ -132,7 +132,7 @@ close. 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. -| id | Territory | Seam | Default today | +| 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 | @@ -143,6 +143,10 @@ default is never a requirement: there is always a documented way to swap it. | 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, wired out of the box +where the territory is built. Where a territory has no implementation yet, the +default names what it will be built with. + This doc defines no contract for these territories. Each belongs to its own RFC. T5 is the one exception. From 5870e6316b0c3ae4e2f3a0051ea4f518dda1d05c Mon Sep 17 00:00:00 2001 From: Samuel Denani Date: Wed, 7 Oct 2026 16:45:52 -0300 Subject: [PATCH 09/13] docs: add the classification rule and the amendment mechanism (#11) Co-Authored-By: Claude Opus 5 --- docs/loopwright/principles.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/loopwright/principles.md b/docs/loopwright/principles.md index cf65b69..06cdfec 100644 --- a/docs/loopwright/principles.md +++ b/docs/loopwright/principles.md @@ -172,4 +172,18 @@ 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. From b7b01bed7d531fd80fbe720227ce4a8781b16b5d Mon Sep 17 00:00:00 2001 From: Samuel Denani Date: Wed, 7 Oct 2026 16:50:38 -0300 Subject: [PATCH 10/13] docs: reconcile the principles doc with RFCs #6-#10 Co-Authored-By: Claude Opus 5 --- docs/loopwright/principles.md | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/docs/loopwright/principles.md b/docs/loopwright/principles.md index 06cdfec..adca72d 100644 --- a/docs/loopwright/principles.md +++ b/docs/loopwright/principles.md @@ -8,7 +8,7 @@ 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 preconditions of autonomy, not constraints on it. +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 @@ -52,8 +52,8 @@ For how work flows from an issue to a merged change, see - `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. +- **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. @@ -96,7 +96,7 @@ For how work flows from an issue to a merged change, see - **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), or `failed` (always blocks). + 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` @@ -115,7 +115,9 @@ 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 and is not the policy file P7 names. +P3. What separates it from the policy file is the author: the host writes the +policy file, and the engine writes the baseline. The host commits the baseline +and never edits it. **An artifact exists before production.** An issue and a PR exist before anything reaches production, because P1 requires the artifact. Without it @@ -123,20 +125,23 @@ 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 is not a completed entry; it names a gap to -close. +`enforced by:` line is empty is complete as documentation. The empty line +records that nothing enforces the opinion yet, so what is incomplete is the +engine, not the entry. 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. +default is never a requirement: where a territory has a seam, there is a +documented way to swap it. T4 and T6 have no seam and promise 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 | +| T3 | Where and how agents execute | `runtime` | `docker` container | | T4 | Thresholds and tolerances | — | already config-driven | | T5 | Agent host | `agent-host` | Claude Code | | T6 | Engine implementation language | — | Node | @@ -147,7 +152,7 @@ A default is the recommended choice for that territory, wired out of the box where the territory is built. Where a territory has no implementation yet, the default names what it will be built with. -This doc defines no contract for these territories. Each belongs to its own +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 @@ -164,8 +169,9 @@ agent host: 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. +Capability 2 is load-bearing for P10. A reviewer is meant to be read-only by +the tools it is handed, not by being told to behave. The guarantee is only as +strong as that tool list, and a write-capable tool in it weakens the guarantee. The engine has zero coupling here: nothing under `.loopwright/scripts/` depends on Claude, agents, skills or sessions. From 7a4274d032f7b1cbf9033636e097cba1c34965c2 Mon Sep 17 00:00:00 2001 From: Samuel Denani Date: Wed, 7 Oct 2026 16:52:13 -0300 Subject: [PATCH 11/13] fix: restore local session as T3's default and tighten the default rule (#11) Co-Authored-By: Claude Opus 5 --- docs/loopwright/principles.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/loopwright/principles.md b/docs/loopwright/principles.md index adca72d..91080b7 100644 --- a/docs/loopwright/principles.md +++ b/docs/loopwright/principles.md @@ -141,16 +141,16 @@ documented way to swap it. T4 and T6 have no seam and promise no swap route. |---|---|---|---| | 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` | `docker` container | +| 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, wired out of the box -where the territory is built. Where a territory has no implementation yet, the -default names what it will be built with. +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. From 2ece483a0e0b958a7f8162e4c200b8c7c58d32e4 Mon Sep 17 00:00:00 2001 From: Samuel Denani Date: Wed, 7 Oct 2026 17:10:52 -0300 Subject: [PATCH 12/13] fix: correct the swap rule, empty-pointer, baseline and capability wording (#11) Co-Authored-By: Claude Opus 5 --- docs/loopwright/principles.md | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/docs/loopwright/principles.md b/docs/loopwright/principles.md index 91080b7..f9f61ae 100644 --- a/docs/loopwright/principles.md +++ b/docs/loopwright/principles.md @@ -115,9 +115,9 @@ 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 the author: the host writes the -policy file, and the engine writes the baseline. The host commits the baseline -and never edits it. +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 @@ -125,17 +125,17 @@ 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 is complete as documentation. The empty line -records that nothing enforces the opinion yet, so what is incomplete is the -engine, not the entry. The gap is closed by building the enforcement, never -by writing a pointer to a file that does not enforce the opinion. +`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: where a territory has a seam, there is a -documented way to swap it. T4 and T6 have no seam and promise no swap route. +default is never a requirement: there is always a documented way to swap it. +The one exception is T6, the engine's implementation language, which has no +swap route. | id | Territory | Seam | Default | |---|---|---|---| @@ -152,8 +152,8 @@ 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. +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. @@ -169,9 +169,8 @@ agent host: 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 meant to be read-only by -the tools it is handed, not by being told to behave. The guarantee is only as -strong as that tool list, and a write-capable tool in it weakens the guarantee. +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. From 8c45702b39dc48361ed437cbac6dc0f5f5622706 Mon Sep 17 00:00:00 2001 From: Samuel Denani Date: Wed, 7 Oct 2026 17:26:20 -0300 Subject: [PATCH 13/13] docs: fix P10 pointer claim, backtick changesets, name swap-rule exception (#11) Co-Authored-By: Claude Opus 5 --- docs/loopwright/principles.md | 6 +++--- docs/specs/issue-11.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/loopwright/principles.md b/docs/loopwright/principles.md index f9f61ae..bc89b05 100644 --- a/docs/loopwright/principles.md +++ b/docs/loopwright/principles.md @@ -134,8 +134,8 @@ opinion. **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 is T6, the engine's implementation language, which has no -swap route. +The one exception to the swap rule is T6, the engine's implementation +language, which has no swap route. | id | Territory | Seam | Default | |---|---|---|---| @@ -145,7 +145,7 @@ swap route. | 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 | +| 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 diff --git a/docs/specs/issue-11.md b/docs/specs/issue-11.md index 313582e..a6af092 100644 --- a/docs/specs/issue-11.md +++ b/docs/specs/issue-11.md @@ -151,7 +151,7 @@ are file paths with **no line number**. | P7 | `.loopwright/scripts/lib/paths.mjs` | `CONFIG_PATH` is a single constant at `:11`, and `quality-gate.mjs:49` / `run-report.mjs:39` read only it | | P8 | `.claude/skills/babysit-pr/SKILL.md` | `:56` "**Merging is the user's decision — never merge.**" | | P9 | `.claude/agents/coder.md` | `:12` "## TDD loop (mandatory for plan steps)", `:15`, `:33` | -| P10 | `.claude/agents/reviewer.md` | frontmatter `:4` `tools: Read, Grep, Glob, Bash` — no write tool | +| P10 | `.claude/agents/reviewer.md` | frontmatter `:4` `tools: Read, Grep, Glob, Bash` — no Write/Edit, though Bash is write-capable; the three sibling reviewers carry `Read, Grep, Glob` only | | P11 | `.claude/agents/sub-issue-reviewer.md` | `:27-28` rejects a horizontal layer | | P12 | `.loopwright/scripts/lib/collect-metrics.mjs` | `COLLECTOR_METRICS` at `:76` is the closed id map |