From ac5cf5935d9b80ca1172af7900b161db65c25382 Mon Sep 17 00:00:00 2001 From: baku-ccron Date: Sun, 16 Aug 2026 08:19:36 +0000 Subject: [PATCH 1/2] /nm and /ncc send back what they can articulate Same rule /nr took in #297. /nm gains human_rule and files the leak itself rather than naming a command for a human to type; /ncc records the rule and the gap that stops it taking its own: there is no typed tool for close-candidate reject, and human_rule_issue refuses needs-work on a live flag. Uphold and keep-open stay the human's in both, because destroying or freezing work is theirs to order. Plugin 0.21.0 -> 0.22.0. Co-Authored-By: Claude Opus 5 (1M context) --- .claude-plugin/marketplace.json | 2 +- plugins/human-fsm/.claude-plugin/plugin.json | 2 +- plugins/human-fsm/commands/ncc.md | 21 +++++- plugins/human-fsm/commands/nm.md | 77 ++++++++++++-------- 4 files changed, 66 insertions(+), 36 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 6d6d999a..c3b849a8 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -10,7 +10,7 @@ "name": "human-fsm", "source": "./plugins/human-fsm", "description": "Slash commands for the human's side of the issue→PR pipeline FSM: read the next ai:ready PR and check the vetter's verdict against the diff and the issue it closes, read the next ai:close-candidate flag and check the producer's reason against the issue and the code it claims about, read the next ai:design PR and check the raised question against the issue and the code it is about, read the next FSM leak and locate which of three defects left the PR in no modeled state, then rule on a close-candidate, send work back as needs-work, rule a design question, or keep an issue open — plus an observation command that forces a run, watches it, measures what its context cost and reads the retained trace corpus for what a run still hand-rolls. Every input comes from pr-review-report; none writes GitHub state itself.", - "version": "0.21.0", + "version": "0.22.0", "author": { "name": "Rain Open Source Software Ltd" }, "keywords": [ "rainlanguage", diff --git a/plugins/human-fsm/.claude-plugin/plugin.json b/plugins/human-fsm/.claude-plugin/plugin.json index 559d70b2..00cb9201 100644 --- a/plugins/human-fsm/.claude-plugin/plugin.json +++ b/plugins/human-fsm/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "human-fsm", "displayName": "Human FSM transitions", - "version": "0.21.0", + "version": "0.22.0", "description": "Slash commands for the human's side of the rainlanguage issue→PR pipeline finite state machine: the reads that precede a ruling, the transitions that make it, and /observe-run — force a run, watch it, measure its context, and read the retained trace corpus for what a run still hand-rolls. Every input arrives from pr-review-report — each ruling as one subcommand, /nr, /ncc, /ndd and /nm as typed MCP reads — and the guards (vocabulary, mandatory note, provenance anchor, stranded-flag refusal, subject type, terminal-is-moot, idempotence) live in that binary, not here.", "author": { "name": "Rain Open Source Software Ltd", diff --git a/plugins/human-fsm/commands/ncc.md b/plugins/human-fsm/commands/ncc.md index b9a38859..1baeeff3 100644 --- a/plugins/human-fsm/commands/ncc.md +++ b/plugins/human-fsm/commands/ncc.md @@ -220,6 +220,23 @@ issue, reassembled by hand, is a decision whose inputs nobody can audit. If a tool is unavailable, say so and stop — the answer is to connect the plugin's MCP server, not to work around it. +## If you can articulate it, send it BACK — not forward + +**Anything you can put into words against the flag is a reject.** Do not write +it up for the human to reach the same answer; the words are the reject's note. + +Two exits stay theirs, and both for the same reason — they destroy or freeze +work: `uphold`, which closes somebody's issue, and `keep-open`, which forbids +the producer from ever flagging it again. + +**Blocked today:** there is no typed tool for `reject`. The server exposes +`human_close` (uphold) and `human_rule_issue`, and the latter refuses +`needs-work` on a live flag because it would strand it. So the reject is handed +to the human as `/close-candidate reject ` — the one exit this +command cannot take itself, and a gap worth closing rather than living with. + +## Typed reads + The grant is three typed reads and nothing else. All three are reads; this command writes no GitHub state. That list is a **declaration, not a sandbox** — measured on Claude Code 2.1.220, a command granting only `Read` still ran a @@ -293,7 +310,7 @@ The rulings are the existing ones and this command invents none: - `/design ` — the flag raises a question a human has to settle rather than a claim anyone can check. -**This command does not rule and does not close.** It is the read that precedes -the human's word. +**This command does not close** — destroying work is the human's word. It DOES +reject what it can articulate against, once a typed reject exists. Names collide across plugins; `/human-fsm:ncc` disambiguates. diff --git a/plugins/human-fsm/commands/nm.md b/plugins/human-fsm/commands/nm.md index 0d8356d3..d559fc84 100644 --- a/plugins/human-fsm/commands/nm.md +++ b/plugins/human-fsm/commands/nm.md @@ -1,7 +1,7 @@ --- description: The next unmodelled PR — an FSM leak the lane classifier buckets into no modeled state — located as a defect in exactly one of three places, against an independent read of the PR, its trusted comments, and the classifier's own rule. argument-hint: [1-3] -allowed-tools: mcp__plugin_human-fsm_fsm__next_leak, mcp__plugin_human-fsm_fsm__pr_context, mcp__plugin_human-fsm_fsm__pr_checkout, mcp__plugin_human-fsm_fsm__clone_release, Skill, Read +allowed-tools: mcp__plugin_human-fsm_fsm__next_leak, mcp__plugin_human-fsm_fsm__pr_context, mcp__plugin_human-fsm_fsm__pr_checkout, mcp__plugin_human-fsm_fsm__clone_release, mcp__plugin_human-fsm_fsm__human_rule, Skill, Read --- Arguments: `$ARGUMENTS` @@ -22,21 +22,17 @@ deliverable of this command is naming which: - **The PR's state record is wrong.** It belongs in an existing state and the label went missing or was hand-mangled. The finding is the state it belongs - in, and the ONE command that files it there, written out in full so the human - can type it: - - `/human-fsm:needs-work ` — work is owed on it; the note - is the work order, and the producer is the next mover. - - `/human-fsm:design ` — the note is the ANSWER to a - question the PR raises, which is itself producer work. - - `/human-fsm:close-candidate uphold ` — the PR is - finished or should be destroyed. This is the close: it runs `human-close`, - which resolves PR-or-issue by lookup, posts the ruling, closes the subject, - and retires any pending flag. It does NOT need an `ai:close-candidate` flag - to already exist, which matters here because a leaked PR by definition - carries no `ai:*` label at all. - - One command, because a state reached by a sequence of hand edits is a state - nothing can audit, which is how this PR got here. + in, and you FILE it there in one typed call rather than naming a command for + someone else to run: + - `human_rule` — `needs-work` when work is owed, `design` when the note is the + ANSWER to a question the PR raises. Both land `ai:needs-work`; the verb + records which ruled, and `rework` is what the producer executes. + - The close stays the human's, because destroying work is theirs to order: + `/human-fsm:close-candidate uphold `. It needs no + existing flag, which matters because a leaked PR carries no `ai:*` at all. + + One call, because a state reached by hand edits is a state nothing can audit — + which is how this PR got here. The commonest cause is a label the classifier NO LONGER RECOGNISES, and there are three cases worth telling apart before you diagnose one: @@ -64,10 +60,11 @@ deliverable of this command is naming which: extra steps. - **The classifier is wrong.** The PR IS in a modeled condition and the machinery fails to see it. The fix is the classifier, never the instance: - hand-patching the PR would clear the box while leaving the defect armed for - the next PR shaped like it. The finding names the defect precisely enough to - file, and it is filed as an issue on the pipeline repo — by the human, since - this command writes nothing. + re-filing the PR would clear the box while leaving the defect armed for the + next PR shaped like it. So the classifier finding goes to the human, stated + precisely enough to file as an issue on the pipeline repo — and it does NOT + substitute for the send-back where this PR also has work owed on it. The + structural fix and the send-back are both true at once. Naming a state and its consuming transition is the whole job. A leak "processed" with a plausible label and no diagnosis is the machine's account of itself @@ -152,11 +149,13 @@ checkouts are how this box filled its disk. If `pr_checkout` ERRORED there is nothing to release: re-call it ONCE, and if it fails again present the read without that half and say so in as many words. -**7. Present the location, not a disposition.** Print every field of the row. -Then say which of the three places the defect is in and the finding that follows -from it — the state and its ONE filing command, or the missing state and its -consuming transition for the design path, or the classifier defect stated -precisely enough to file. Then say what you checked to conclude it: the note, +**7. Locate it, act on it, and report both.** Print every field of the row. Then +say which of the three places the defect is in and the finding that follows from +it — the state, or the missing state and its consuming transition, or the +classifier defect stated precisely enough to file. Then TAKE the send-back per +**If you can articulate it** below, and report that you took it. A located leak +you handed onward is a leak that is still leaking. Then say what you checked to +conclude it: the note, the labels, the rule, and the code where you read any. A leak whose location you cannot determine is a complete and correct outcome STATED AS SUCH — say what you read and what would decide it, rather than defaulting to the nearest plausible @@ -191,9 +190,18 @@ typed: - `/human-fsm:keep-open ` — protects an ISSUE from being re-flagged; not a move on a PR. -**This command does not rule, does not label, and does not file** — it is the -read that precedes the human's word, and on this queue the human's word is a -diagnosis. +## If you can articulate it, send it BACK — not forward + +**Anything you can put into words about why this PR is not merged is a +send-back.** Rule it with `human_rule`; the words you were about to write for +the human ARE the work order, and they go in `rework`. + +Diagnose first anyway — a leak located in the wrong place is a defect left +armed. Then act. The structural finding and the send-back are both true at +once, never alternatives. + +Not yours to move: `keep-open` on an issue, `close-candidate reject` on a +flagged subject. ## Typed reads, and no shell at all @@ -204,10 +212,15 @@ reassembled by hand outside the machine, is the same defect it is diagnosing. If a tool is unavailable, say so and stop — the answer is to connect the plugin's MCP server, not to work around it. -The grant is four typed calls plus `Skill` and `Read`, and `Read` applies to the -`pr_checkout` tree and nothing else. All four typed calls are reads except -`clone_release`, which disposes of what this command itself created and writes -no GitHub state. That list is a **declaration, not a sandbox**: measured on +The grant is five typed calls plus `Skill` and `Read`, and `Read` applies to the +`pr_checkout` tree and nothing else. Four are reads; `clone_release` disposes of +what this command itself created and writes no GitHub state. + +The fifth, `human_rule`, is the send-back and the only call that writes GitHub +state. Typed for the same reason as every other input: its guards — mandatory +work order, head-sha anchor, clearing every other `ai:*` — live in the binary, +and a state reached by hand edits is the unauditable record this queue exists to +diagnose. That list is a **declaration, not a sandbox**: measured on Claude Code 2.1.220, a command granting only `Read` still ran a `Bash` call with no permission denial. So the prohibition above is the thing that actually binds, which is why it is written here rather than assumed of the frontmatter — and why From e393b6e1bd094d848d8cf569b6a12ecb4563d209 Mon Sep 17 00:00:00 2001 From: baku-ccron Date: Sun, 16 Aug 2026 08:35:52 +0000 Subject: [PATCH 2/2] style: deno fmt nm.md and nr.md The `denofmt` pre-commit hook reflows both files to 80 columns; the prose is hand-wrapped and does not match. Whitespace only. `nr.md` is main's, red there too and fixed identically in #300; carried here because a branch inherits its base's red step. Co-Authored-By: Claude Opus 5 (1M context) --- plugins/human-fsm/commands/nm.md | 22 +++++++++++----------- plugins/human-fsm/commands/nr.md | 24 ++++++++++++------------ 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/plugins/human-fsm/commands/nm.md b/plugins/human-fsm/commands/nm.md index d559fc84..3c76bf23 100644 --- a/plugins/human-fsm/commands/nm.md +++ b/plugins/human-fsm/commands/nm.md @@ -155,12 +155,12 @@ it — the state, or the missing state and its consuming transition, or the classifier defect stated precisely enough to file. Then TAKE the send-back per **If you can articulate it** below, and report that you took it. A located leak you handed onward is a leak that is still leaking. Then say what you checked to -conclude it: the note, -the labels, the rule, and the code where you read any. A leak whose location you -cannot determine is a complete and correct outcome STATED AS SUCH — say what you -read and what would decide it, rather than defaulting to the nearest plausible -label, because a guessed re-filing is indistinguishable from a located one and -wrong in the way nothing downstream can detect. +conclude it: the note, the labels, the rule, and the code where you read any. A +leak whose location you cannot determine is a complete and correct outcome +STATED AS SUCH — say what you read and what would decide it, rather than +defaulting to the nearest plausible label, because a guessed re-filing is +indistinguishable from a located one and wrong in the way nothing downstream can +detect. ## Why this is a second opinion and not a dispatcher @@ -197,8 +197,8 @@ send-back.** Rule it with `human_rule`; the words you were about to write for the human ARE the work order, and they go in `rework`. Diagnose first anyway — a leak located in the wrong place is a defect left -armed. Then act. The structural finding and the send-back are both true at -once, never alternatives. +armed. Then act. The structural finding and the send-back are both true at once, +never alternatives. Not yours to move: `keep-open` on an issue, `close-candidate reject` on a flagged subject. @@ -220,9 +220,9 @@ The fifth, `human_rule`, is the send-back and the only call that writes GitHub state. Typed for the same reason as every other input: its guards — mandatory work order, head-sha anchor, clearing every other `ai:*` — live in the binary, and a state reached by hand edits is the unauditable record this queue exists to -diagnose. That list is a **declaration, not a sandbox**: measured on -Claude Code 2.1.220, a command granting only `Read` still ran a `Bash` call with -no permission denial. So the prohibition above is the thing that actually binds, +diagnose. That list is a **declaration, not a sandbox**: measured on Claude Code +2.1.220, a command granting only `Read` still ran a `Bash` call with no +permission denial. So the prohibition above is the thing that actually binds, which is why it is written here rather than assumed of the frontmatter — and why nothing in this file is fenced as a shell line, because what a reader copies out of a command is what the command showed them. diff --git a/plugins/human-fsm/commands/nr.md b/plugins/human-fsm/commands/nr.md index b5ac0f68..9d4ff36a 100644 --- a/plugins/human-fsm/commands/nr.md +++ b/plugins/human-fsm/commands/nr.md @@ -259,13 +259,13 @@ reason. Both rulings land there; the verb only records which one ruled, so choosing between them is not a decision about where the PR goes. The words you were about to write for the human ARE the work order: they go in -`--rework`, which is what makes the send-back executable. A send-back whose -work order says nothing has sent nothing back. +`--rework`, which is what makes the send-back executable. A send-back whose work +order says nothing has sent nothing back. -**What goes forward is what you have nothing to say against.** That is the -whole of it — a PR you read against its issue, ran the lens over, and found -nothing articulable to raise. Then the only remaining question is the human's -to answer: merge it or not. +**What goes forward is what you have nothing to say against.** That is the whole +of it — a PR you read against its issue, ran the lens over, and found nothing +articulable to raise. Then the only remaining question is the human's to answer: +merge it or not. That list is a **declaration, not a sandbox**: measured on Claude Code 2.1.220, a command granting only `Read` still ran a `Bash` call with no permission @@ -331,10 +331,10 @@ Print every field of the row. Then give the independent read — what the issue asked for, what the diff does, whether those two agree — then the audit lens's findings, each marked as the diff's own code or as surrounding context, and then say where the whole of that and the vetter's note diverge. Then say what it adds -up to — and if the legacy deploy signal is -`repo-not-migrated`, say that too, as repo health rather than as a gate: the -merge does not wait on any deploy (#162), and what the signal asks for is the -repo's migration to the split release lifecycle. +up to — and if the legacy deploy signal is `repo-not-migrated`, say that too, as +repo health rather than as a gate: the merge does not wait on any deploy (#162), +and what the signal asks for is the repo's migration to the split release +lifecycle. **The lens's findings arrive under the scope they were formed at, stated.** Name the literal you declared — `pr:`, with the number in it — or, where the @@ -350,7 +350,7 @@ pointed at the source, and say which those were. **This command does not merge** — the merge is the human's word on a PR they named. It DOES rule: everything it can articulate goes back, and what reaches -the human is the read that precedes their word on a PR with nothing said -against it. +the human is the read that precedes their word on a PR with nothing said against +it. Names collide across plugins; `/human-fsm:nr` disambiguates.