The problem: approvals and commands were exact, case-sensitive matches. A phone keyboard's Approve counted as rejection feedback and threw the plan away. Stop didn't reach the cancel flag while work ran. Other capitalised commands got a hint instead of acting.
Change:gates.command_form() defines how a message reads as a command. Case, surrounding whitespace, runs of inner whitespace and a trailing . or ! don't matter. It's used by:
the approval gate (approve, approve rev-N);
every command in the orchestrator: stop (checked on the transport thread), status, abandon, use, start milestone <n>, next milestone, project complete, and the restart choices (which already ignored case and now share the function).
Unchanged:
Only matching is affected: the project name in use <name> keeps its case (Use Demo. → Demo), and feedback and answers are recorded and passed on exactly as written.
A stale revision is still refused, e.g. Approve REV-1 at rev-2.
Approval still needs the whole message to be the command: "I approve of most of it" is feedback.
Sender checks are unchanged; they come from the event, not the text.
Tests: gate cases (Approve, APPROVE, Approve., approve!, Approve rev-2, a capitalised stale revision, a sentence containing "approve"); a full capitalised command flow through the orchestrator; and the stop-while-busy test parametrized over stop, Stop! and STOP. 9 new cases fail on main; the sentence case is a guard. 475 unit tests and the integration suite pass; lint and format clean.
Closes #64.
**The problem:** approvals and commands were exact, case-sensitive matches. A phone keyboard's `Approve` counted as **rejection feedback** and threw the plan away. `Stop` didn't reach the cancel flag while work ran. Other capitalised commands got a hint instead of acting.
**Change:** `gates.command_form()` defines how a message reads as a command. Case, surrounding whitespace, runs of inner whitespace and a trailing `.` or `!` don't matter. It's used by:
- the approval gate (`approve`, `approve rev-N`);
- every command in the orchestrator: `stop` (checked on the transport thread), `status`, `abandon`, `use`, `start milestone <n>`, `next milestone`, `project complete`, and the restart choices (which already ignored case and now share the function).
**Unchanged:**
- **Only matching is affected:** the project name in `use <name>` keeps its case (`Use Demo.` → `Demo`), and feedback and answers are recorded and passed on exactly as written.
- **A stale revision is still refused**, e.g. `Approve REV-1` at rev-2.
- **Approval still needs the whole message to be the command:** "I approve of most of it" is feedback.
- **Sender checks are unchanged**; they come from the event, not the text.
**Tests:** gate cases (`Approve`, `APPROVE`, `Approve.`, `approve!`, ` Approve rev-2 `, a capitalised stale revision, a sentence containing "approve"); a full capitalised command flow through the orchestrator; and the stop-while-busy test parametrized over `stop`, `Stop!` and ` STOP `. 9 new cases fail on `main`; the sentence case is a guard. 475 unit tests and the integration suite pass; lint and format clean.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
A phone keyboard's "Approve" did not match the approval pattern, so it
counted as rejection feedback and threw the plan away; "Stop" did not
reach the cancel flag; other capitalised commands got a hint instead of
acting.
gates.command_form() is how a message reads as a command: case,
surrounding whitespace, inner runs of whitespace and a trailing '.' or
'!' do not matter. The approval gate and every command match in the
orchestrator (stop, status, abandon, use, start milestone, next
milestone, project complete, the restart choices) use it. Only matching
changes: the project name in 'use <name>' keeps its case, and feedback
and answers are recorded and passed on as written. A stale revision is
still refused, and approval still needs the whole message to be the
command.
Closes#64.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Closes #64.
The problem: approvals and commands were exact, case-sensitive matches. A phone keyboard's
Approvecounted as rejection feedback and threw the plan away.Stopdidn't reach the cancel flag while work ran. Other capitalised commands got a hint instead of acting.Change:
gates.command_form()defines how a message reads as a command. Case, surrounding whitespace, runs of inner whitespace and a trailing.or!don't matter. It's used by:approve,approve rev-N);stop(checked on the transport thread),status,abandon,use,start milestone <n>,next milestone,project complete, and the restart choices (which already ignored case and now share the function).Unchanged:
use <name>keeps its case (Use Demo.→Demo), and feedback and answers are recorded and passed on exactly as written.Approve REV-1at rev-2.Tests: gate cases (
Approve,APPROVE,Approve.,approve!,Approve rev-2, a capitalised stale revision, a sentence containing "approve"); a full capitalised command flow through the orchestrator; and the stop-while-busy test parametrized overstop,Stop!andSTOP. 9 new cases fail onmain; the sentence case is a guard. 475 unit tests and the integration suite pass; lint and format clean.🤖 Generated with Claude Code
https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE