From 2c2045e051cadcc217c318637c9dfb3eecbfef60 Mon Sep 17 00:00:00 2001 From: David Meister Date: Thu, 13 Aug 2026 19:14:05 +0000 Subject: [PATCH 1/3] feat: the standing repo conventions become a skill, not a per-brief retype MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nine constraints bind every agent working in a rainlanguage repo — clone and scratch isolation, the acts reserved to the human, the `## QA` gate, the environment shapes a run breaks on — and none of them was shipped anywhere. They were hand-copied into brief after brief, which pays to restate an invariant every time it is needed and offers a fresh chance to restate it wrongly or drop one. `rain-repo-conventions` is a third plugin publishing ONE skill. It is neither actor's: `human-fsm` is the human's transitions and `vetter-judgement` is the machine vetter's judgement, while this is what is true AROUND the work, for whoever is doing it. It versions separately because it changes separately — a plugin's content is its release, and welding it to either of the others would bump a version every installer reinstalls for something that did not change. A `--agents` brief was the alternative and it does not reach far enough: it binds only agents the cron itself dispatches, while the hand-copying was happening in ordinary sessions on the box. A marketplace skill reaches every session that installed it. ## Grouped by what KIND of rule each entry is The grouping is the content, because it decides what a reader does with an entry: - RULES are chosen and do not expire. Nothing about the box lifts one. - FACTS are true of the environment rather than of the work, each stated so a reader can check it — the day the box changes the entry is simply wrong, and a stale fact carried as belief is worse than no entry. - WORKAROUNDS route around a defect elsewhere and each names what would retire it, so the debt has an owner and an exit. An agent that cannot tell which it is holding argues with a fact, verifies a prohibition, or treats a piece of debt as permanent. ## What was judged rather than transcribed - `jq is not on PATH` is narrowed to "on PATH only inside the flake devshell". The unqualified form contradicts this repo's own live worker brief, which tells every dispatched worker that `gh` and `jq` are on PATH — true there because `campaign-run.sh` wraps the type with `jq`. `gh --jq` is the form that holds either way, so that is what the entry prescribes. - The `--body-file` literal-path requirement moves from workaround to FACT. The hook reads a command line with a lexer that resolves quoting and nothing else, and CLAUDE.md is explicit that it is not bash and never will be — a guard that expanded shell variables would have to BE a shell. Nothing is coming to fix it. - The generated `.pre-commit-config.yaml` is a WORKAROUND with a named fix, not a standing rule: rainix devshells run git-hooks-nix's shellHook, which writes the file on entry, and a `.gitignore` line retires the instruction per repo. Surveyed across the checkouts on this box, roughly half the org's flake repos already carry that line and the rest do not. - The QA block's SUBSTANCE stays in QA-GUIDE.md, where `require-qa-block` and the vetter enforce it. The skill records only that the gate exists and what shape it refuses — one fact, one surface. - The scratch-path incident comes out and its MECHANISM stays, per the properties-never-cases rule the marketplace already ships under. A case rots when its subject changes state and invites reasoning by analogy; the applicable half is that a shared path hands one agent's numbers to another agent's PR body. Nothing was cut for being repo-specific: every entry holds across at least a family of repos, and the two narrowest (soldeer, the pre-commit config) are scoped in their own text to the Foundry and rainix repos they bind. Co-Authored-By: Claude Opus 5 (1M context) --- .claude-plugin/marketplace.json | 15 +++ README.md | 74 ++++++++--- .../.claude-plugin/plugin.json | 22 ++++ plugins/rain-repo-conventions/README.md | 41 +++++++ .../skills/rain-repo-conventions/SKILL.md | 116 ++++++++++++++++++ 5 files changed, 250 insertions(+), 18 deletions(-) create mode 100644 plugins/rain-repo-conventions/.claude-plugin/plugin.json create mode 100644 plugins/rain-repo-conventions/README.md create mode 100644 plugins/rain-repo-conventions/skills/rain-repo-conventions/SKILL.md diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 637bf38..b2ef2b2 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -35,6 +35,21 @@ "judgement", "github" ] + }, + { + "name": "rain-repo-conventions", + "source": "./plugins/rain-repo-conventions", + "description": "The standing constraints on any agent doing work in a rainlanguage repo, shipped once so a brief stops restating them. One skill, grouped by what KIND of rule each entry is: rules that are chosen and never expire (clone and scratch isolation for parallel agents, the irreversible acts reserved to the human, assignment, never departing from an agreed spec, never reporting a run you did not watch finish), facts that are true of the box and the harness rather than of the work (the `## QA` gate on `gh pr create` and its literal-path argument, force-backgrounded builds, `jq` only inside the devshell, a `pgrep` wait that matches itself), and workarounds that name the defect they route around and what would retire them (the soldeer bump sequence, the generated pre-commit config). A wrong fact carried as belief is worse than no entry, so each is stated so a reader can check it — and it states properties, never cases.", + "version": "0.1.0", + "author": { "name": "Rain Open Source Software Ltd" }, + "keywords": [ + "rainlanguage", + "conventions", + "agents", + "tooling", + "github", + "foundry" + ] } ] } diff --git a/README.md b/README.md index d444fa8..0ce70c4 100644 --- a/README.md +++ b/README.md @@ -436,6 +436,43 @@ and a quietly worse vetter. An autonomous pipeline's behaviour is a pure function of its committed code and prompts, and a rule worth remembering is a rule worth shipping. +### The standing conventions as a skill + +The third thing this marketplace publishes is for **neither** actor in +particular: `rain-repo-conventions` is ONE skill holding what is true around the +work in any rainlanguage repo — the isolation a parallel agent needs, the +irreversible acts reserved to the human, the gate a `gh pr create` passes +through, and the environment facts whose shape a run breaks on. + +``` +/plugin marketplace add rainlanguage/issue-pr-cron +/plugin install rain-repo-conventions@issue-pr-cron +``` + +**A brief is the wrong place for something that never varies.** These +constraints were hand-copied into agent brief after agent brief, which pays to +restate an invariant every time it is needed and offers a fresh chance to +restate it wrongly or omit one — the same argument that moved the worker rules +out of the improvised dispatch prompt and into +[`campaign-worker-prompt.txt`](#briefing-a-dispatched-worker--rules-not-state). +A `--agents` brief reaches only agents the cron itself dispatches; a marketplace +skill reaches every session on any box that installed it, which is where the +hand-copying was happening. + +**It is grouped by the KIND of rule, and the grouping is the content.** _Rules_ +are chosen and do not expire. _Facts_ are true of the environment rather than of +the work — each stated so a reader can check it, because the day the box changes +the entry is simply wrong, and a stale fact carried as belief is worse than no +entry. _Workarounds_ route around a defect elsewhere and each names what would +retire it. An agent that cannot tell which it is holding argues with a fact, +verifies a prohibition, or treats a piece of debt as permanent. + +**It states properties, never cases**, for the reasons the section above gives. +It is also not a gate: the `## QA` block's substance stays in +[QA-GUIDE.md](QA-GUIDE.md) where `require-qa-block` and the vetter enforce it, +and this records only that the gate exists and what shape it refuses — one fact, +one surface. Where it and a repo's own `CLAUDE.md` disagree, the repo wins. + ### The vetter's transitions as an MCP surface For the producer, routing through the tool is enforced by the **prompt**, and a @@ -2287,24 +2324,25 @@ and evidence that answers a narrower question than the issue asked. ## Files (tracked here) -| File | Purpose | -| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `campaign-run.sh` | Durable runner (built as the `campaign-run` flake package): `flock` single-run lock, `DISABLED` kill-switch, `timeout`, invokes `claude --print` with the prompt + settings, logs to `campaign.log` (+ per-run JSONL traces in `runs/`). Nix builds its PATH; it sets none itself. | -| `campaign-prompt.txt` | The campaign instructions fed to the model. | -| `campaign-worker-prompt.txt` | The standing brief every DISPATCHED worker starts with. `campaign-run.sh` wraps it into the `pr-worker` subagent type with `jq` and passes it as `--agents`, so the harness loads it straight into each dispatched agent and the main loop pays none of those bytes. See [Briefing a dispatched worker](#briefing-a-dispatched-worker--rules-not-state). | -| `campaign-settings.json` | Tool allow/deny list passed via `--settings` (the permission guardrails). | -| `review-run.sh` | Vetting runner (same hardened pattern as `campaign-run.sh`): vets open PRs on the MCP surface, logs to `review.log`. Its one GitHub write is `record_verdict`. Kill-switch `review-DISABLED`. | -| `review-prompt.txt` | The AI-vetting instructions fed to the model: the judgement gates only — every `gh` recipe is a tool schema instead. | -| `review-auditor-prompt.txt` | The standing brief every DISPATCHED auditor starts with. `review-run.sh` wraps it into the `pr-auditor` subagent type with `jq` and passes it as `--agents`, with a `tools` list that is the READ half of the vetter's surface and no write at all. See [Fanning the audit out](#fanning-the-audit-out--and-keeping-the-verdict). | -| `review-settings.json` | Tool allow/deny for the vetter: the eight `mcp__fsm__*` tools + `Read`/`Glob`/`Grep`/`Skill`/`Task`/`ToolSearch`, **Bash denied outright**. `Task` is the dispatch tool the audit fan-out needs; every write tool stays denied, and a session deny reaches inside a dispatched agent too. | -| `review-mcp.json` | The vetter's MCP config: one stdio server, `pr-review-report mcp`, named `fsm` (so its tools are `mcp__fsm__*`). | -| `campaign-mcp.json` | MCP config for the producer's clone-lifecycle surface: one stdio server, `pr-review-report mcp --profile producer`, named `fsm`. Additive — the producer keeps its Bash. | -| `cron.env.example` | Template for deployment-specific values (PR assignee, work dir, models, run caps). Copy to `cron.env` (gitignored) and edit. | -| `pr-review-report.sh` | Thin wrapper (flake package `pr-review-report-sh`) over the binary. Reports every open PR by its pipeline stage (approved / AI-vetted / needs-producer-fix (red) / needs-work / close / unreviewed / pending / draft — a conflicted ready PR reports as needs-work, the state it is owed), reading `ai:*`/`human:*` labels + GitHub approvals, as clickable URLs. | -| `hooks/` | The two bash PreToolUse guards that close deny-list bypasses. See [PreToolUse guards](#pretooluse-guards--what-a-prompt-cannot-hold). | -| `.claude-plugin/` | The marketplace listing this repo publishes. Its version must match the plugin manifest's — `pr-review-report plugin-version-lockstep` is the gate. | -| `plugins/human-fsm/` | The human's slash commands as a Claude Code plugin. Prompts only: every guard is in the binary. See [The human's slash commands](#the-humans-slash-commands). | -| `plugins/vetter-judgement/` | The machine vetter's judgement as a Claude Code skill — properties, never cases — loaded on demand at verdict time rather than carried in `review-prompt.txt` on every run. See [The vetter's judgement as a skill](#the-vetters-judgement-as-a-skill). | +| File | Purpose | +| -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `campaign-run.sh` | Durable runner (built as the `campaign-run` flake package): `flock` single-run lock, `DISABLED` kill-switch, `timeout`, invokes `claude --print` with the prompt + settings, logs to `campaign.log` (+ per-run JSONL traces in `runs/`). Nix builds its PATH; it sets none itself. | +| `campaign-prompt.txt` | The campaign instructions fed to the model. | +| `campaign-worker-prompt.txt` | The standing brief every DISPATCHED worker starts with. `campaign-run.sh` wraps it into the `pr-worker` subagent type with `jq` and passes it as `--agents`, so the harness loads it straight into each dispatched agent and the main loop pays none of those bytes. See [Briefing a dispatched worker](#briefing-a-dispatched-worker--rules-not-state). | +| `campaign-settings.json` | Tool allow/deny list passed via `--settings` (the permission guardrails). | +| `review-run.sh` | Vetting runner (same hardened pattern as `campaign-run.sh`): vets open PRs on the MCP surface, logs to `review.log`. Its one GitHub write is `record_verdict`. Kill-switch `review-DISABLED`. | +| `review-prompt.txt` | The AI-vetting instructions fed to the model: the judgement gates only — every `gh` recipe is a tool schema instead. | +| `review-auditor-prompt.txt` | The standing brief every DISPATCHED auditor starts with. `review-run.sh` wraps it into the `pr-auditor` subagent type with `jq` and passes it as `--agents`, with a `tools` list that is the READ half of the vetter's surface and no write at all. See [Fanning the audit out](#fanning-the-audit-out--and-keeping-the-verdict). | +| `review-settings.json` | Tool allow/deny for the vetter: the eight `mcp__fsm__*` tools + `Read`/`Glob`/`Grep`/`Skill`/`Task`/`ToolSearch`, **Bash denied outright**. `Task` is the dispatch tool the audit fan-out needs; every write tool stays denied, and a session deny reaches inside a dispatched agent too. | +| `review-mcp.json` | The vetter's MCP config: one stdio server, `pr-review-report mcp`, named `fsm` (so its tools are `mcp__fsm__*`). | +| `campaign-mcp.json` | MCP config for the producer's clone-lifecycle surface: one stdio server, `pr-review-report mcp --profile producer`, named `fsm`. Additive — the producer keeps its Bash. | +| `cron.env.example` | Template for deployment-specific values (PR assignee, work dir, models, run caps). Copy to `cron.env` (gitignored) and edit. | +| `pr-review-report.sh` | Thin wrapper (flake package `pr-review-report-sh`) over the binary. Reports every open PR by its pipeline stage (approved / AI-vetted / needs-producer-fix (red) / needs-work / close / unreviewed / pending / draft — a conflicted ready PR reports as needs-work, the state it is owed), reading `ai:*`/`human:*` labels + GitHub approvals, as clickable URLs. | +| `hooks/` | The two bash PreToolUse guards that close deny-list bypasses. See [PreToolUse guards](#pretooluse-guards--what-a-prompt-cannot-hold). | +| `.claude-plugin/` | The marketplace listing this repo publishes. Its version must match the plugin manifest's — `pr-review-report plugin-version-lockstep` is the gate. | +| `plugins/human-fsm/` | The human's slash commands as a Claude Code plugin. Prompts only: every guard is in the binary. See [The human's slash commands](#the-humans-slash-commands). | +| `plugins/vetter-judgement/` | The machine vetter's judgement as a Claude Code skill — properties, never cases — loaded on demand at verdict time rather than carried in `review-prompt.txt` on every run. See [The vetter's judgement as a skill](#the-vetters-judgement-as-a-skill). | +| `plugins/rain-repo-conventions/` | What is true around the work in ANY rainlanguage repo, as a Claude Code skill — grouped into rules that do not expire, environment facts to check, and workarounds that name what would retire them. See [The standing conventions as a skill](#the-standing-conventions-as-a-skill). | ## Briefing a dispatched worker — rules, not state diff --git a/plugins/rain-repo-conventions/.claude-plugin/plugin.json b/plugins/rain-repo-conventions/.claude-plugin/plugin.json new file mode 100644 index 0000000..3a3645a --- /dev/null +++ b/plugins/rain-repo-conventions/.claude-plugin/plugin.json @@ -0,0 +1,22 @@ +{ + "name": "rain-repo-conventions", + "displayName": "Rain repo conventions", + "version": "0.1.0", + "description": "The standing constraints on any agent doing work in a rainlanguage repo, shipped once so a brief stops restating them. One skill, grouped by what KIND of rule each entry is: rules that are chosen and never expire (clone and scratch isolation for parallel agents, the irreversible acts reserved to the human, assignment, never departing from an agreed spec, never reporting a run you did not watch finish), facts that are true of the box and the harness rather than of the work (the `## QA` gate on `gh pr create` and its literal-path argument, force-backgrounded builds, `jq` only inside the devshell, a `pgrep` wait that matches itself), and workarounds that name the defect they route around and what would retire them (the soldeer bump sequence, the generated pre-commit config). A wrong fact carried as belief is worse than no entry, so each is stated so a reader can check it — and it states properties, never cases.", + "author": { + "name": "Rain Open Source Software Ltd", + "url": "https://github.com/rainlanguage" + }, + "homepage": "https://github.com/rainlanguage/issue-pr-cron", + "repository": "https://github.com/rainlanguage/issue-pr-cron", + "license": "LicenseRef-DCL-1.0", + "keywords": [ + "rainlanguage", + "conventions", + "agents", + "tooling", + "github", + "foundry" + ], + "skills": "./skills/" +} diff --git a/plugins/rain-repo-conventions/README.md b/plugins/rain-repo-conventions/README.md new file mode 100644 index 0000000..3281931 --- /dev/null +++ b/plugins/rain-repo-conventions/README.md @@ -0,0 +1,41 @@ +# rain-repo-conventions — what is true around the work + +One skill. Every agent working in a rainlanguage repo is bound by the same +standing constraints, and until they were shipped somewhere they were +hand-copied into brief after brief — which is a per-brief cost paid to restate +something that never varies, and a per-brief opportunity to restate it wrongly +or leave one out. + +It is grouped by the KIND of rule each entry is, because that is what decides +what a reader does with it: + +| Section | What the group is | +| --------------- | ---------------------------------------------------------------------------------------------------------- | +| **Rules** | chosen, and they do not expire — nothing about the box lifts one | +| **Facts** | true of the environment rather than of the work — stated so you can check one, wrong the day the box changes | +| **Workarounds** | a route around a defect elsewhere, each naming what would retire it | + +The split is not decoration. A rule is argued with, a fact is verified, and a +workaround is a piece of debt with an owner — an agent that cannot tell which it +is holding treats a stale environment fact as a prohibition, or a prohibition as +something to route around. + +**It states properties, never cases.** No PR numbers, no dates, no incidents. An +instruction that needs a worked example to be obeyed is underspecified; a cited +case rots when its subject changes state, and its context cost is paid on every +load. The one anecdote the source list carried — a shared scratch path that +nearly put one repo's test numbers in another repo's PR body — is here as the +mechanism instead, which is the half that is actually applicable. + +**It is not a gate and it does not replace one.** The `## QA` block's content is +[QA-GUIDE.md](https://github.com/rainlanguage/issue-pr-cron/blob/main/QA-GUIDE.md)'s, +enforced by `pr-review-report require-qa-block` and judged by the vetter; this +records only that the gate is there and what shape it refuses. Where this file +and a repo's own `CLAUDE.md` disagree, the repo wins. + +## Install + +``` +/plugin marketplace add rainlanguage/issue-pr-cron +/plugin install rain-repo-conventions@issue-pr-cron +``` diff --git a/plugins/rain-repo-conventions/skills/rain-repo-conventions/SKILL.md b/plugins/rain-repo-conventions/skills/rain-repo-conventions/SKILL.md new file mode 100644 index 0000000..e089e15 --- /dev/null +++ b/plugins/rain-repo-conventions/skills/rain-repo-conventions/SKILL.md @@ -0,0 +1,116 @@ +--- +name: rain-repo-conventions +description: The standing constraints on any agent doing work in a rainlanguage repo, so a brief does not have to restate them — what is never done, what is true of the box and the harness rather than of the work, and what is a route around a defect that should be fixed. Covers clone and scratch isolation for parallel agents, the irreversible acts reserved to the human, the `## QA` gate every `gh pr create` passes through, the force-backgrounding of long builds and how to read one to completion, the wait shapes that never exit, and the soldeer bump sequence. Invoke once per session, before the first clone or the first write. Triggers on "work this issue", "open a PR", "clone the repo to work in", "bump a dependency", "run the test suite", "CI is red", "report the result". +version: 0.1.0 +--- + +# Rain repo conventions + +These bind every agent working in a rainlanguage repo, whether or not its brief +restates them, and they are why a brief does not have to. Nothing here is a +pipeline transition, a verdict, or a tool: it is what is true around the work. + +**Where this and a repo's own `CLAUDE.md` disagree, the repo wins** — it is +closer to the code and it is versioned with it. Where this and the brief you +were given disagree, **say so and stop**; picking one silently is the failure +either way round. + +Three groups, and the group tells you what kind of rule you are reading: + +- **Rules** are chosen. Nothing about the box lifts one and none of them + expires. +- **Facts** are true of the environment, not of the work. Each is stated so you + can check it — when the environment changes the entry is wrong, and a wrong + fact carried as belief is worse than no entry at all. +- **Workarounds** route around a defect somewhere else. Each names what would + retire it. Fixing the defect and deleting the entry is always the better + move. + +## Rules + +- **One fresh clone per agent invocation, at a path no other agent is using.** + Never a git worktree: worktrees share one repository — its refs, its config + and its object store — so two agents in two worktrees are one agent with two + prompts. A distinct clone path is the only isolation the filesystem actually + enforces, and the failure it prevents is silent cross-contamination rather + than an error anyone sees. +- **Scratch files and logs go under a path scoped to the repo being worked, + never a shared session scratchpad.** Parallel agents are handed the same + scratchpad, so an unqualified filename is one another agent is also writing: + what you read back is another repo's numbers, and you will report them as + this repo's. A count, a log or a diff read out of a shared path is evidence + for nothing. +- **Never merge a PR, delete a branch, create a tag, dispatch a deploy workflow + or broadcast a transaction. Never force-push, in any spelling.** Every one of + them either destroys work the next agent cannot recover or moves state a + human then has to live with. Performing one is taking a decision, not doing + the work you were asked for. +- **Assign every PR and issue you open to `thedavidmeister`.** An unassigned + subject has no inbox: it is found only by someone already looking for it. +- **Never depart from an agreed spec without asking first — including to turn a + red CI green.** A red that reflects reality is information, and the damage in + papering over it is that the information is destroyed rather than read. Ask, + and say what the red actually reports. +- **Never report a result you did not watch finish.** "Green" requires the + pass/fail line in front of you — not an exit code you inferred, not a run you + started and left, not a suite that was green on the last head. A claim about a + run you did not read to its end is fabricated whatever the run goes on to do. + +## Facts + +- **`gh pr create` passes through a PreToolUse gate that reads the body before + anything is created.** The body must carry a literal `## QA` heading and these + four literal lines: + + ``` + ## QA + - Discriminating tests: + - Mutations applied: + - Oracle: + - Category check: + ``` + + An honest `n/a` **with its reason** satisfies a line the change genuinely + cannot have; an absent line does not. The gate checks the block is PRESENT — + what the four lines have to say, and whether their claims hold, is the + producer QA guide's business and the vetter's, not this file's. + +- **Push the branch before `gh pr create`.** The create is where the gate reads + the body, and a create against a branch the remote does not have either fails + outright or drags an interactive push prompt into a session that cannot + answer one. +- **That gate reads a command line, not a shell.** `--body-file` must be a + literal absolute path: a `$VAR` or a `~` in the argument is never expanded, + because the gate resolves quoting and nothing else. This one is not a defect + awaiting a fix — a guard that expanded shell variables would have to BE a + shell, with every bypass that implies. +- **A long `nix`, `forge` or `cargo` command may be force-backgrounded by a + hook.** The call then returns immediately with an output file path in place of + the command's output. Poll that file with `Read` until the command's own + completion line is in it: the return is not the result, and neither is the + last line of a file still being written. +- **`jq` is on PATH only inside the flake devshell.** Outside one it is simply + absent, and a pipeline through it fails in a way that reads like the `gh` call + failing. `gh --jq` takes the same expressions and needs nothing on PATH. +- **`pgrep -f ` matches the searching process's own command line**, so + a wait built on it is satisfied by itself. Do not write + `until ; do sleep; done` at all — poll once in the foreground and move + on, or read the output file above. + +## Workarounds + +- **Bump a soldeer dependency with `soldeer update`, never `soldeer install`.** + `install` resolves from the lock rather than from the bumped manifest and dies + in the remappings step, with an error that names the remapping instead of the + resolution that produced it. `update` then **appends** the new remapping + without pruning the one it replaces: delete the stale line by hand, and check + what still imports through it — a versioned import prefix is deliberate, so a + leftover remapping keeps compiling and pins the old version silently. + _Retired when a bump resolves from the manifest and rewrites the remapping in + place._ +- **Never commit a `.pre-commit-config.yaml`.** Entering a rainix devshell + generates one in the repo root — it is output, regenerated on every entry, and + it looks authored because it arrives untracked next to real files. Much of the + org already `.gitignore`s it and the rest does not. _Retired per repo by that + `.gitignore` line, which is the fix worth making the moment you meet a repo + without it._ From d739d8a1ed7658d9b264809862bf10af1d0dc5d5 Mon Sep 17 00:00:00 2001 From: David Meister Date: Thu, 13 Aug 2026 19:19:57 +0000 Subject: [PATCH 2/3] fix(conventions): the wait entry states a rule its fact did not support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `pgrep -f` matching the searching process is a FACT about the box. "Every wait carries a maximum iteration count and is never keyed on a pattern its own command line contains" is a RULE. They were one entry, and collapsing them produced a ban on the whole `until ; do sleep; done` shape — stronger than the fact underneath it, and a Rule sitting in the Facts section. A bounded loop terminates correctly, so the ban forbade something safe while naming neither thing that actually strands a wait: no iteration bound, and a pattern the searcher's own argv contains. Both are avoidable without banning the shape. Split accordingly. The fact says why a self-matching search can never be satisfied in the direction the loop needs. The rule requires a bound, requires the loop to say what it last saw when it hits it, and states the preference — poll once in the foreground, or read the backgrounded output file; a loop is the last resort, not the default. The unbounded case is stated as a property (a loop whose condition never arrives does not fail, it runs on past the turn that started it, unattended, and they accumulate) rather than as the incident that produced it. README gains the split as the worked argument for why the grouping exists at all: a rule that overreaches its fact is exactly what it is built to catch. Version stays 0.1.0 — the plugin is new in this PR and nothing has installed it, so this is content of the first release rather than a change to a shipped one, which is the case `version-hygiene.yaml`'s bump job exempts by name. Co-Authored-By: Claude Opus 5 (1M context) --- .claude-plugin/marketplace.json | 2 +- README.md | 12 +++++++++++- .../.claude-plugin/plugin.json | 2 +- .../skills/rain-repo-conventions/SKILL.md | 18 +++++++++++++----- 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index b2ef2b2..4e1d6cc 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -39,7 +39,7 @@ { "name": "rain-repo-conventions", "source": "./plugins/rain-repo-conventions", - "description": "The standing constraints on any agent doing work in a rainlanguage repo, shipped once so a brief stops restating them. One skill, grouped by what KIND of rule each entry is: rules that are chosen and never expire (clone and scratch isolation for parallel agents, the irreversible acts reserved to the human, assignment, never departing from an agreed spec, never reporting a run you did not watch finish), facts that are true of the box and the harness rather than of the work (the `## QA` gate on `gh pr create` and its literal-path argument, force-backgrounded builds, `jq` only inside the devshell, a `pgrep` wait that matches itself), and workarounds that name the defect they route around and what would retire them (the soldeer bump sequence, the generated pre-commit config). A wrong fact carried as belief is worse than no entry, so each is stated so a reader can check it — and it states properties, never cases.", + "description": "The standing constraints on any agent doing work in a rainlanguage repo, shipped once so a brief stops restating them. One skill, grouped by what KIND of rule each entry is: rules that are chosen and never expire (clone and scratch isolation for parallel agents, the irreversible acts reserved to the human, assignment, never departing from an agreed spec, never reporting a run you did not watch finish, keeping every wait bounded), facts that are true of the box and the harness rather than of the work (the `## QA` gate on `gh pr create` and its literal-path argument, force-backgrounded builds, `jq` only inside the devshell, a `pgrep` search that finds itself), and workarounds that name the defect they route around and what would retire them (the soldeer bump sequence, the generated pre-commit config). A wrong fact carried as belief is worse than no entry, so each is stated so a reader can check it — and it states properties, never cases.", "version": "0.1.0", "author": { "name": "Rain Open Source Software Ltd" }, "keywords": [ diff --git a/README.md b/README.md index 0ce70c4..4a67dfb 100644 --- a/README.md +++ b/README.md @@ -442,7 +442,8 @@ The third thing this marketplace publishes is for **neither** actor in particular: `rain-repo-conventions` is ONE skill holding what is true around the work in any rainlanguage repo — the isolation a parallel agent needs, the irreversible acts reserved to the human, the gate a `gh pr create` passes -through, and the environment facts whose shape a run breaks on. +through, what makes a wait terminate, and the environment facts whose shape a +run breaks on. ``` /plugin marketplace add rainlanguage/issue-pr-cron @@ -467,6 +468,15 @@ entry. _Workarounds_ route around a defect elsewhere and each names what would retire it. An agent that cannot tell which it is holding argues with a fact, verifies a prohibition, or treats a piece of debt as permanent. +**The split disciplines the entries themselves.** `pgrep -f` matching the +searching process's own command line is a FACT — checkable, and about the box. +"Every wait carries a maximum iteration count and is never keyed on a pattern +its own command line contains" is a RULE. Written as one entry they collapsed +into a ban on the whole `until ; do sleep; done` shape, which is stronger +than the fact underneath it: a bounded loop terminates correctly, so the ban +forbade something safe while naming neither thing that actually strands a wait. +A rule that overreaches its fact is the failure this grouping is built to catch. + **It states properties, never cases**, for the reasons the section above gives. It is also not a gate: the `## QA` block's substance stays in [QA-GUIDE.md](QA-GUIDE.md) where `require-qa-block` and the vetter enforce it, diff --git a/plugins/rain-repo-conventions/.claude-plugin/plugin.json b/plugins/rain-repo-conventions/.claude-plugin/plugin.json index 3a3645a..f326f52 100644 --- a/plugins/rain-repo-conventions/.claude-plugin/plugin.json +++ b/plugins/rain-repo-conventions/.claude-plugin/plugin.json @@ -2,7 +2,7 @@ "name": "rain-repo-conventions", "displayName": "Rain repo conventions", "version": "0.1.0", - "description": "The standing constraints on any agent doing work in a rainlanguage repo, shipped once so a brief stops restating them. One skill, grouped by what KIND of rule each entry is: rules that are chosen and never expire (clone and scratch isolation for parallel agents, the irreversible acts reserved to the human, assignment, never departing from an agreed spec, never reporting a run you did not watch finish), facts that are true of the box and the harness rather than of the work (the `## QA` gate on `gh pr create` and its literal-path argument, force-backgrounded builds, `jq` only inside the devshell, a `pgrep` wait that matches itself), and workarounds that name the defect they route around and what would retire them (the soldeer bump sequence, the generated pre-commit config). A wrong fact carried as belief is worse than no entry, so each is stated so a reader can check it — and it states properties, never cases.", + "description": "The standing constraints on any agent doing work in a rainlanguage repo, shipped once so a brief stops restating them. One skill, grouped by what KIND of rule each entry is: rules that are chosen and never expire (clone and scratch isolation for parallel agents, the irreversible acts reserved to the human, assignment, never departing from an agreed spec, never reporting a run you did not watch finish, keeping every wait bounded), facts that are true of the box and the harness rather than of the work (the `## QA` gate on `gh pr create` and its literal-path argument, force-backgrounded builds, `jq` only inside the devshell, a `pgrep` search that finds itself), and workarounds that name the defect they route around and what would retire them (the soldeer bump sequence, the generated pre-commit config). A wrong fact carried as belief is worse than no entry, so each is stated so a reader can check it — and it states properties, never cases.", "author": { "name": "Rain Open Source Software Ltd", "url": "https://github.com/rainlanguage" diff --git a/plugins/rain-repo-conventions/skills/rain-repo-conventions/SKILL.md b/plugins/rain-repo-conventions/skills/rain-repo-conventions/SKILL.md index e089e15..d79b575 100644 --- a/plugins/rain-repo-conventions/skills/rain-repo-conventions/SKILL.md +++ b/plugins/rain-repo-conventions/skills/rain-repo-conventions/SKILL.md @@ -1,6 +1,6 @@ --- name: rain-repo-conventions -description: The standing constraints on any agent doing work in a rainlanguage repo, so a brief does not have to restate them — what is never done, what is true of the box and the harness rather than of the work, and what is a route around a defect that should be fixed. Covers clone and scratch isolation for parallel agents, the irreversible acts reserved to the human, the `## QA` gate every `gh pr create` passes through, the force-backgrounding of long builds and how to read one to completion, the wait shapes that never exit, and the soldeer bump sequence. Invoke once per session, before the first clone or the first write. Triggers on "work this issue", "open a PR", "clone the repo to work in", "bump a dependency", "run the test suite", "CI is red", "report the result". +description: The standing constraints on any agent doing work in a rainlanguage repo, so a brief does not have to restate them — what is never done, what is true of the box and the harness rather than of the work, and what is a route around a defect that should be fixed. Covers clone and scratch isolation for parallel agents, the irreversible acts reserved to the human, the `## QA` gate every `gh pr create` passes through, the force-backgrounding of long builds and how to read one to completion, what makes a wait terminate, and the soldeer bump sequence. Invoke once per session, before the first clone or the first write. Triggers on "work this issue", "open a PR", "clone the repo to work in", "bump a dependency", "run the test suite", "wait for CI", "CI is red", "report the result". version: 0.1.0 --- @@ -55,6 +55,14 @@ Three groups, and the group tells you what kind of rule you are reading: pass/fail line in front of you — not an exit code you inferred, not a run you started and left, not a suite that was green on the last head. A claim about a run you did not read to its end is fabricated whatever the run goes on to do. +- **A wait is bounded, and never keyed on a pattern its own command line + contains.** Every loop carries a maximum iteration count and says what it last + saw when it reaches it, because an unbounded loop whose condition never + arrives does not fail — it runs on past the turn that started it, invisible + and unattended, and they accumulate. The self-matching pattern below is the + other way one never terminates. Prefer neither shape: poll once in the + foreground and move on, or read the backgrounded output file. A loop is the + last resort, not the default. ## Facts @@ -92,10 +100,10 @@ Three groups, and the group tells you what kind of rule you are reading: - **`jq` is on PATH only inside the flake devshell.** Outside one it is simply absent, and a pipeline through it fails in a way that reads like the `gh` call failing. `gh --jq` takes the same expressions and needs nothing on PATH. -- **`pgrep -f ` matches the searching process's own command line**, so - a wait built on it is satisfied by itself. Do not write - `until ; do sleep; done` at all — poll once in the foreground and move - on, or read the output file above. +- **`pgrep -f ` matches the searching process's own command line.** The + pattern sits in the argv of the shell doing the looking, so the search finds + itself and goes on finding itself after the process it is watching is gone. A + wait keyed on it can never be satisfied in the direction it needs. ## Workarounds From 8bfb4bf3784f6abf211b13ced1f7169b3bce8361 Mon Sep 17 00:00:00 2001 From: David Meister Date: Thu, 13 Aug 2026 19:23:26 +0000 Subject: [PATCH 3/3] style: prettier reflow, and the generated config stated as what it actually is MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `static / rs-static` runs `pre-commit run --all-files`, whose bundle includes prettier, so the markdown this PR adds is CI-gated the same as the code. The two new files were hand-wrapped and prettier disagreed about six paragraphs; nothing else in the tree moved, including the re-padded README table. While confirming it: entering the rainix rust-shell created the `.pre-commit-config.yaml` the skill's last entry is about. It is a SYMLINK into the nix store, rewritten on every entry — which is a sharper and more checkable statement than "generated", so the entry now says it. That is the group's own standard: a fact is written so a reader can verify it, and this one was verified by the run that gated this commit. `pre-commit run --all-files` now exits 0 with all eleven hooks passing. Co-Authored-By: Claude Opus 5 (1M context) --- plugins/rain-repo-conventions/README.md | 8 ++--- .../skills/rain-repo-conventions/SKILL.md | 36 +++++++++---------- 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/plugins/rain-repo-conventions/README.md b/plugins/rain-repo-conventions/README.md index 3281931..5e15ff0 100644 --- a/plugins/rain-repo-conventions/README.md +++ b/plugins/rain-repo-conventions/README.md @@ -9,11 +9,11 @@ or leave one out. It is grouped by the KIND of rule each entry is, because that is what decides what a reader does with it: -| Section | What the group is | -| --------------- | ---------------------------------------------------------------------------------------------------------- | -| **Rules** | chosen, and they do not expire — nothing about the box lifts one | +| Section | What the group is | +| --------------- | ------------------------------------------------------------------------------------------------------------ | +| **Rules** | chosen, and they do not expire — nothing about the box lifts one | | **Facts** | true of the environment rather than of the work — stated so you can check one, wrong the day the box changes | -| **Workarounds** | a route around a defect elsewhere, each naming what would retire it | +| **Workarounds** | a route around a defect elsewhere, each naming what would retire it | The split is not decoration. A rule is argued with, a fact is verified, and a workaround is a piece of debt with an owner — an agent that cannot tell which it diff --git a/plugins/rain-repo-conventions/skills/rain-repo-conventions/SKILL.md b/plugins/rain-repo-conventions/skills/rain-repo-conventions/SKILL.md index d79b575..75ce40a 100644 --- a/plugins/rain-repo-conventions/skills/rain-repo-conventions/SKILL.md +++ b/plugins/rain-repo-conventions/skills/rain-repo-conventions/SKILL.md @@ -23,8 +23,7 @@ Three groups, and the group tells you what kind of rule you are reading: can check it — when the environment changes the entry is wrong, and a wrong fact carried as belief is worse than no entry at all. - **Workarounds** route around a defect somewhere else. Each names what would - retire it. Fixing the defect and deleting the entry is always the better - move. + retire it. Fixing the defect and deleting the entry is always the better move. ## Rules @@ -37,14 +36,14 @@ Three groups, and the group tells you what kind of rule you are reading: - **Scratch files and logs go under a path scoped to the repo being worked, never a shared session scratchpad.** Parallel agents are handed the same scratchpad, so an unqualified filename is one another agent is also writing: - what you read back is another repo's numbers, and you will report them as - this repo's. A count, a log or a diff read out of a shared path is evidence - for nothing. + what you read back is another repo's numbers, and you will report them as this + repo's. A count, a log or a diff read out of a shared path is evidence for + nothing. - **Never merge a PR, delete a branch, create a tag, dispatch a deploy workflow or broadcast a transaction. Never force-push, in any spelling.** Every one of - them either destroys work the next agent cannot recover or moves state a - human then has to live with. Performing one is taking a decision, not doing - the work you were asked for. + them either destroys work the next agent cannot recover or moves state a human + then has to live with. Performing one is taking a decision, not doing the work + you were asked for. - **Assign every PR and issue you open to `thedavidmeister`.** An unassigned subject has no inbox: it is found only by someone already looking for it. - **Never depart from an agreed spec without asking first — including to turn a @@ -85,8 +84,8 @@ Three groups, and the group tells you what kind of rule you are reading: - **Push the branch before `gh pr create`.** The create is where the gate reads the body, and a create against a branch the remote does not have either fails - outright or drags an interactive push prompt into a session that cannot - answer one. + outright or drags an interactive push prompt into a session that cannot answer + one. - **That gate reads a command line, not a shell.** `--body-file` must be a literal absolute path: a `$VAR` or a `~` in the argument is never expanded, because the gate resolves quoting and nothing else. This one is not a defect @@ -113,12 +112,11 @@ Three groups, and the group tells you what kind of rule you are reading: resolution that produced it. `update` then **appends** the new remapping without pruning the one it replaces: delete the stale line by hand, and check what still imports through it — a versioned import prefix is deliberate, so a - leftover remapping keeps compiling and pins the old version silently. - _Retired when a bump resolves from the manifest and rewrites the remapping in - place._ -- **Never commit a `.pre-commit-config.yaml`.** Entering a rainix devshell - generates one in the repo root — it is output, regenerated on every entry, and - it looks authored because it arrives untracked next to real files. Much of the - org already `.gitignore`s it and the rest does not. _Retired per repo by that - `.gitignore` line, which is the fix worth making the moment you meet a repo - without it._ + leftover remapping keeps compiling and pins the old version silently. _Retired + when a bump resolves from the manifest and rewrites the remapping in place._ +- **Never commit a `.pre-commit-config.yaml`.** Entering a rainix devshell drops + one in the repo root — a symlink into the nix store, rewritten on every entry. + It is output, not source, and it looks authored because it arrives untracked + next to real files. Much of the org already `.gitignore`s it and the rest does + not. _Retired per repo by that `.gitignore` line, which is the fix worth + making the moment you meet a repo without it._