Gates and verdicts
Commands that judge documents, plans, branches and merges, and commands that record a decision about a verdict. A verdict is the exit code of the command; the output carries the findings that explain it. Findings have a severity: error fails the verdict, warn is reported and fails it only in strict mode — set by the project's readiness.strict for task-ready and by --strict for plan-ready — and info is context.
validate
Runs the audit of the project documents and the checks configured for the project, and prints the verdict with its findings. Without arguments it runs the full profile. With flags it runs the profile you name; the command run is added for you. Findings are grouped by class, and the full state is written to docs/validation/STATUS.md.
npx noomoo validate [--profile edit|commit|full|task-ready|plan-ready] [--task <TASK-NNN>] [--fix]Flags: --profile chooses the set of checks, --task marks the run with a task (findings stay corpus-wide), --fix repairs the one-way form errors it can repair (broken paragraphs, task document shape) and says what it changed.
Exit: 0 — green; 1 — red, at least one error finding; 2 — --profile has no value, or the run itself failed. An unknown profile runs no stages and exits 0.
Pull request acceptance
In CI, on a pull_request event, validate runs the pr-acceptance stage. It reads the branch name and the diff of the pull request from its merge base with the target branch, and picks one of five lanes. You do not call the stage yourself: the workflow that setup installs runs it on every pull request.
- Task branch. The branch name carries a task key, such as
feature/<KEY>-login. The stage accepts the phases of that task, asphase-check --phase alldoes, and every failed check becomes anerrorfindingphase-not-accepted. - Light lane. The branch matches
lightLane.branches(by defaultfix/**) and has no task key. A short fix is judged by its size and by a test in the diff instead of task documents:light-lane-too-large,light-lane-no-test. - Service branch. The branch matches
git.serviceBranches(by defaultchore/**,docs/**,release/**). It needs no task as long as its diff carries no code. If it carries code, it is judged as a task branch: with a task key in its name (chore/<KEY>-deps) the phases of that task are accepted; without one the stage reportstask-undeterminedand names the first code file. The light lane does not apply to a service branch that carries code: rename the branch tofix/…or give it a task key. - Integration branch. The branch is the target branch itself (
git.targetBranch, for example a pull request fromdeveloptomain). Its commits were accepted in their own pull requests, so the stage passes it with a stated reason. - Installation. On a target branch without noomoo, a service branch that only installs noomoo is accepted as the installation.
What a service branch carries without a task
A file in the diff of a service branch is not code when it is one of these:
- a file under
docs/or.agent/,CHANGELOG.mdorREADME.md; - an image or a video, in any directory:
.png,.jpg,.jpeg,.gif,.webp,.mp4,.webm. Only the last extension of the name counts, in any letter case, somedia/demo.GIFpasses andx.png.jsis code..svgis code: a browser runs the markup of an SVG file, scripts included, so an SVG change gets the same review as any other code. The list is fixed in the core and has no setting; package.json,package-lock.jsonand the plugin manifest that differ only in theirversionfield;- an update of the noomoo core from the same source: a semver range in
package.json, and a lock entry from the samehttpsregistry path with a non-empty integrity hash. Any other specifier (github:,git+…,file:, a URL), another address or a missing hash is code; - git hooks and the noomoo workflow equal to what the core template renders from
config/pipeline.jsonat the merge base of the pull request, not at its head: a branch cannot change the configuration it is compared with.
Anything else is code. Paths are compared as git stores them, so non-ASCII file names (media/демо.png) follow the same rules.
When a pull request changes the noomoo core
The workflow installs dependencies from the lock file of the pull request and then runs npx noomoo validate. So a pull request is judged by the core that it installs itself. A branch that rolls the core back, or points it at another source, is judged in CI by that older or foreign core.
To make such a change visible, the stage reports core-version-changed with severity warn on every lane, with the old and the new value and the kind of change: update, rollback, source change or core removed. For a source change both addresses are shown. The finding does not fail the run, so routine core updates stay green.
The finding is written by the core of the pull request itself, so it catches mistakes, not intent. A rollback to a core released before this check reports nothing, and a deliberately swapped core can stay silent. What protects against that is a review by someone other than the author of the pull request:
- a
.github/CODEOWNERSfile that names an owner forpackage.json,package-lock.json,pnpm-lock.yamland.github/workflows/; - a branch protection rule on the target branch that requires a review from code owners and the CI check to pass.
Branch protection is available on public repositories and on paid GitHub plans. The approval must come from a different GitHub account: an agent that works under the owner's token cannot approve its own pull request, and such a review proves nothing.
task-ready
Judges whether a task document is ready to be implemented. It runs the deterministic checks of the task and then asks a reviewing model, through the Claude CLI, to judge the document against the readiness rubric; the text of the document and the rubric are passed to the model. The report is written to docs/tasks/reviews/<id>-review.md (the id in lower case). A repeated run on an unchanged document reuses the accepted review. The number of review rounds is limited; when the limit is reached the verdict is left to a person.
npx noomoo task-ready <TASK-NNN> [--no-cache]Flags: --no-cache forces a new review instead of reusing the accepted one. Strictness comes from the project's readiness.strict (strict by default: warn findings fail the verdict); --strict is refused with exit code 2.
Exit: 0 — ready; 1 — needs rework; 2 — no task given; 3 — the round budget is exhausted and a person must decide; 4 — the task is ready but its predecessors are not closed; 5 — the review timed out; 6 — the reviewing model could not be started.
plan-ready
Judges the implementation plan of a task. The deterministic checks always run: sections, test expectations, paths, coverage of the task's criteria and the decision protocol. The reviewing model is asked only with --judge, or always with --strict; the text of the plan and the rubric are passed to it. With the reviewing model, the report is written to docs/tasks/reviews/<id>-plan-review.md (the id in lower case).
npx noomoo plan-ready <TASK-NNN> [--judge] [--no-cache] [--strict]Flags: --judge asks the reviewing model for a second opinion, --no-cache forces a new review, --strict asks the model and makes warn findings fail the verdict.
Exit: 0 — the plan is ready; 1 — needs rework; 2 — no task given; 3 — the round budget is exhausted and a person must decide; 5 — the review timed out; 6 — the reviewing model could not be started.
phase-check
Accepts a phase of the work on a task by machine. It runs the checks of the phase (for example plan-approved, tests-in-diff, gate-green, pr-open, axis-closed and code-review-closed). It runs git fetch for the target branch and reads the pull request through gh; for the later phases (all, 6 and 7) it runs the full validate, which rewrites docs/validation/STATUS.md and prints every check, so the output can be pasted into a report as evidence. Phase 0 has no machine checks, and the command says so.
npx noomoo phase-check <TASK-NNN> [--phase 0|1|2|3|4|5|6|7|all] [--check <id,id>] [--json]Flags: --phase names the phase (default all), --check runs the named checks instead of the phase set, --json prints the result as JSON.
Exit: 0 — every check passed; 1 — a check failed, the phase has no machine checks, no task was given, or the configuration cannot be read.
code-review
Opens a round of code review for a task. It creates the review artifact on the first round and a new section for each next round, with a code CR-NN slot for the reviewer's remarks. The command writes no remarks; the reviewer adds them to the section. The round is signed with a GitHub login.
npx noomoo code-review <TASK-NNN> --who <login> [--kind human|agent] [--model <model>] [--docs <path>]Flags: --who is the GitHub login that signs the round, --kind says whether a person or an agent reviews, --model names the model of an agent reviewer, --docs points to a documents directory other than docs.
Exit: 0 — the round was opened; 1 — refused, with the reason on standard error; 2 — wrong arguments.
Test cases removed between rounds
A test case the branch removes between two rounds does not reach the merge unnoticed. From the second round on, the command lists every case that the branch's own commits removed since the previous round's head, one heading per file and case title, with no decision under it:
### Removed case · tests/review.test.ts › creates the draft when Review application is clickedThe reviewer writes a decision under each heading, in the same form as for a CR-NN remark: - Decision: fixed in commit <hash> when the case was restored, or - Decision: refusal — <reason> when the removal is accepted. Removing a case stays allowed; the machine only checks that the decision is written. Until every listed case has a decision, phase-check fails code-review-closed and names the file, the case and the round.
What counts as removed: the declaration line of an it or test case (or a pytest def test…) is gone and the same title of the same runner family did not appear in any file. A case moved to another file or describe block, a case removed and added back, and an edit of a case body without a new title are not removals. Removing an already skipped case counts. A case removed by a merge of the target branch is not the branch's removal, even in a file the branch edited too; a case dropped while resolving a merge conflict is not listed either. Cases removed after the last round make code-review-fresh ask for a new round, and that round lists them.
stamp
Puts a decision stamp: the approval line of a plan, the basis cell of an axis in a plan or feature protocol, or the decision line of a gate report. The author of the stamp is the GitHub login of the token in use (see identity), and the kind of run comes from the environment. The command has no --who flag and rejects it. A refusal leaves the file untouched.
npx noomoo stamp <TASK-NNN> --plan
npx noomoo stamp <TASK-NNN> --axis "<axis>" --state "<state>" [--feature <FEAT-NNN>]
npx noomoo stamp <TASK-NNN> --gate task|plan --reason "<reason>"
npx noomoo stamp --intake <NNN>Flags: --plan approves the plan, --axis and --state stamp an axis of the decision protocol, --feature addresses the protocol of a feature, --gate and --reason record a decision on a gate report, --intake approves intake proposal NNN with the approval line of the document.
Who may approve an intake depends on the project setting stamps.intakeBy in config/pipeline.json:
"human"(the default): only a person approves. A run of kindagentis refused, and the refusal names the setting: it gives the owner's command and the"intakeBy": "agent"line that lets an executor approve."agent": an agent run approves as well. When the project declares roles (roles), the login must hold the owner, tech lead or implementer role; without declared roles any login may, as for other stamps. Its approval line carries a fourth field, theagentmark:- Approved: <hash> · <login> · <date> · agent. The mark tells readers and checks that an executor approved on the owner's word, not the owner in person. A person still approves without the mark. A CI run never approves.
Turn agent on only if you trust the executor to carry the owner's answer over from the chat: the core cannot verify that answer. While the setting is agent, the intake stage accepts marked approvals. If the setting is off, each marked approval is an error finding intake-approved-by-agent, and no requirement may rest on that intake until the owner approves it in person. The same applies to a mark typed by hand, in any letter case, and next then advises the owner to approve that intake again. Switching the setting from human to agent in a branch is reported by pull request acceptance as policy-weakening-requires-owner, like any other weakening of the stamp rules: it is the owner's decision, not the executor's. Switching it back reports nothing. The intake stage reads the setting from the tree it checks, which in CI is the head of the pull request: a branch that turns the setting on and approves intakes in the same change is stopped by pull request acceptance, not by the intake stage.
The command commits what it writes itself, with a subject built from git.commitTemplate.
Approving a plan again on the same day, after you edited it, writes the same approval line as before: same login, same kind of run, same date. The plan-approved check of phase-check dates an approval by the commit where that line appeared, so on its own the second stamp would leave the check comparing the plan with the first approval and reporting it as changed after approval. To avoid that, the command makes two commits in this case. The first withdraws the previous approval: the plan without its approval line. The second puts the approval back together with its stamp log record. The final plan text is the same as before the command, and the check now compares the plan with the new approval. If the first commit is refused, the plan is put back as it was. If the second is refused, the withdrawal stays in the history, the plan on disk has no approval line, and the message names the withdrawal commit: fix the cause and run stamp --plan again.
Exit: 0 — the stamp was put; 1 — refused, with the reason on standard error; 2 — wrong arguments, including --who.
evidence
Prints the decision trail of a task: every gate round from its gate log — gate, time, outcome, whether it passed, revision, judge model, provider, transport and prompt hash, findings by level — then the stamps from its stamp log and the rounds of its code review with reviewer, head, findings and those without a decision, removed test cases included. Every record is marked as a local record: the command calls no judge, and anything read from repository files may have been written by the executor whose work it vouches for, so independence is not proven. Fields an older round did not record are null. The command only reads and leaves no record of its own call.
npx noomoo evidence TASK-NNN [--json]Flags: --json prints the trail as JSON (schema version, task, authority, gates, stamps, code review).
Exit: 0 — the trail was printed, empty or not; 2 — no task id, a malformed id, an unknown flag, a task the project does not have, or a trail file that cannot be read.
gate-accept
Records the decision of a person when a gate stopped with its round budget exhausted, or when its last call to the reviewing model gave no answer (a timeout, a model that could not be started, a reply that could not be read). Only a person may decide in place of a model that gave no answer, and not after the model already returned a failing verdict on the same revision. The decision is a stamp from the confirmed identity of the author, with a reason. By default it addresses the report of the task gate; --plan addresses the report of the plan gate.
npx noomoo gate-accept <TASK-NNN> --reason "<reason>" [--plan] [--docs <path>]Flags: --reason states the basis of the decision, --plan addresses the plan gate, --docs points to a documents directory other than docs. A --who flag is rejected.
Exit: 0 — the decision was recorded; 1 — refused, with the reason on standard error; 2 — wrong arguments.
findings-accept
Accepts every finding of a type, optionally in one file, with a basis and an expiry date, or marks one finding of the last run as false. Accepted findings leave the verdict until the date; the record is kept under docs/validation/. A false mark says that the rule was wrong here; stats collects such marks. The address of a mark must point at exactly one finding of the last run.
npx noomoo findings-accept --type <code> --reason "<reason>" --until <YYYY-MM-DD> --who "<name>" [--file <path>] [--docs <path>]
npx noomoo findings-accept --false --type <code> --file <path> --subject "<substring>" --reason "<reason>" --who "<name>" [--docs <path>]Flags: --type is the finding code, --reason the basis, --until the expiry date, --who the name of the person, --file limits the acceptance to one file, --false switches to the false mark, --subject selects the finding by a substring of its message, --docs points to a documents directory other than docs.
Exit: 0 — recorded; 2 — wrong arguments, or the address of a false mark does not select exactly one finding.
accepted-migrate
Moves the records of accepted.json that rest on message text onto the code address of the finding. The old form by key and the "false" mark by a substring of the message are matched against the findings of the last run; each one covered by exactly one finding that has a code gets the msgId and params of that finding (and its task). After that, changing the language or the wording of a message does not detach the record. A record that no finding covers, that is covered by a finding without a code, or that is covered by more than one finding is left as it is and named in the output. A rule by type and file does not depend on the text and is not touched. A second run changes nothing. Run validate first: the command reads the findings of the last run.
npx noomoo accepted-migrate [--docs <path>]Flags: --docs points to a documents directory other than docs.
Exit: 0 — done, with the count of records rewritten and the records left as they were; 2 — there are no findings of the last run to match against.
merge-green
Judges a merge by the CI run of the pull request head. Free GitHub plans do not protect branches, so a pull request with a failed or cancelled run can be merged. The command takes the pull request number from the subject of the merge commit (taken from --subject, then COMMIT_SUBJECT, then the last commit), reads the run of the named workflow on the head of that pull request through the GitHub API, and fails when the merge went in over a run that was not green. A closing commit of the task axis has no pull request and passes. The result is printed as a GitHub Actions annotation line. It needs a token and the repository name (see Environment). The same judgement runs as a stage of validate, but only in GitHub Actions on a push to the target branch; elsewhere that stage reports nothing.
npx noomoo merge-green [--workflow "<name>"] [--subject "<subject>"] [--sha "<commit>"]Flags: --workflow names the CI workflow to read, --subject names the subject to judge instead of the last commit; --sha names the commit whose parents are read when the subject has no pull request number. A merge commit whose second and later parents came from pull requests with a green run passes.
Exit: 0 — the merge is lawful, or the commit is a closing commit of the task axis; 1 — the merge bypassed a green run, the commit did not come from a pull request, there is no token or repository name, or the judgement could not be made.
commit-msg
The commit-message hook. It reads the message file git passes and refuses a message that carries an AI authorship mark. When the editor was not opened (-m, -F), the whole message is judged. When it was, the diff slice added by git commit -v is left out.
npx noomoo commit-msg <message-file>Flags: none.
Exit: 0 — the message is clean; 1 — a mark was found, and standard error names the lines; 2 — the message file is missing or unreadable.
bypass
Makes a skipped gate visible. The command does not disable hooks; a bypass is made with git --no-verify. The command records the reason, the author and the debt that stays visible in every run until it is closed.
npx noomoo bypass --reason "<reason>" --who "<name>"
npx noomoo bypass --close <id>
npx noomoo bypass --audit [--range <from>..<to>]Flags: --reason and --who open a debt, --close runs the gate itself, with the profile the pre-commit hook uses on the current branch (full on the target branch, commit elsewhere), and closes a debt (by the id printed when it was opened) only if that run passes, --audit lists commits that have no green run, --range limits the audit to a commit range.
Exit: 0 — done, or every audited commit has a green run; 1 — the audit found commits without a green run; 2 — wrong arguments, a wrong range, nothing to close, or the gate run by --close is red.
stats
Summarises the project's own telemetry from local records: the rounds of the gates, the runs of validate, the project event log, the false marks of findings-accept, and the time from a green run to the merge. It writes nothing.
npx noomoo stats [--since <YYYY-MM-DD>] [--json]Flags: --since limits the summary to events from the date, --json prints the summary as JSON.
Exit: 0 — the summary was built; 2 — wrong arguments.