Skip to content

v3.2.0

Choose a tag to compare

@github-actions github-actions released this 26 Sep 21:14
· 64 commits to main since this release
0766fe7

The gates gate what they are given. otito gate and otito review take the repository from the positional or --path, in local and PR mode alike, and every CLI gate, otito pass-pr included, reads policy and governance from the .otitorc.json of the repository it gates, as the MCP gate tools now do too. The GitHub PR gate reads whether a PR is open, merged or closed and reports it as pr.state and pr.mergedAt, new report fields that make this a minor release. A --pr or selector that names no PR is refused on the command line rather than gating the wrong thing; over MCP a blank pr reads as no PR, and pr_merge_readiness with no selector gates the checked-out branch's PR again. Repair hints name the @nugehs packages that exist, and otito help no longer promises the legacy MCP tool names go away at 3.0. No command, field or schema was removed.

Fixed

  • The compliance-controls gate names the bouncer package that exists. When a repository has bouncer.config.json and no bouncer binary resolves, the local gate told you to run npm install --save-dev @bashbop/bouncer, which npm answers with a 404: bouncer is published as @nugehs/bouncer. The hint and the README link now name it. A repository that runs bouncer in CI instead, through a workflow under .github/workflows that references @nugehs/bouncer (such as npx -y @nugehs/bouncer@latest check), now gets a warning that says so and names the workflow line, rather than one that reads as a broken install. It stays a warning: the gate reads the workflow file but never runs npx or installs anything, so it has no evidence that the controls pass for the change it is looking at.
  • The contract-drift gate names the tieline package that exists. When a repository has tieline.config.json and no tieline binary resolves, the local gate told you to run npm install --save-dev @bashbop/tieline, which npm answers with a 404: tieline is published as @nugehs/tieline. The hint and the README link now name it. The README's aiglare link had the same fault and now points at @nugehs/aiglare, where aiglare is published.
  • The GitHub PR gate knows whether a PR is open, merged or closed. otito gate --pr, otito pass-pr, otito review --pr and review_gate { pr } never asked gh for the PR's state, so a merged or closed PR was gated as if it were still open. Once #214 merged, GitHub reported its mergeability as UNKNOWN and the gate answered "GitHub mergeability is not settled yet"; the audit ledger's post-merge records for #195, #196 and #197 carry the same PR state: WARN. A closed PR was judged on the mergeability GitHub last reported, so #188 read as pending and #153 as conflicted. The gate now requests state and mergedAt and reports both under pr. A merged PR's state passes, "PR is already merged; there is nothing left to gate.", and the terminal verdict no longer names a check blocking it or calls it ready to merge. Its review decision, CODEOWNERS, conversation, branch-protection and status checks still run, because they describe what merged and post-merge attestation records them, so a PR merged without CODEOWNERS approval still fails that check. A closed PR's state fails: it cannot merge unless it is reopened.
  • docs/03 and CONTRIBUTING.md said otito runs team governance; it runs solo. The Contributor Governance page called this repository a team repository and ended "Do not copy solo governance onto this repository", and CONTRIBUTING.md said code owners review changes under team governance, while the checked-in .otitorc.json has made solo the default for every otito command run here since #193. Both now say the repository has one maintainer and what solo means for its gate: a missing separate review or CODEOWNERS approval is WARN evidence, not FAIL, requested changes still fail, and an explicit --governance team still overrides the file.
  • The MCP gate tools honour the repository's .otitorc.json, as the CLI does. On 2026-09-26 otito pass-pr 214 gated #214 under solo governance and warned on CODEOWNERS ("solo-maintainer mode requires an explicit owner/admin merge decision"), while review_gate { pr: "214" } failed the same PR under team governance ("CODEOWNERS approval is missing"). This repository checks in .otitorc.json with "governance": "solo". The CLI fills an omitted --policy or --governance from that file and the user config before any gate runs; the MCP server passed its arguments straight through, so a missing governance normalised to team. review_gate and review_verdict, and the merge_readiness, pr_merge_readiness and review_pr aliases that route to them, now fill an omitted or blank policy and governance from the same config, found from the repository at path rather than from wherever the MCP host started the server, and their schemas say so. An explicit argument still wins. Called from a server started outside the repository, review_gate { pr: "214", path } now reports governance: solo, CODEOWNERS WARN and verdict WARN, as otito pass-pr 214 does, and governance: "team" still fails it. The lookup also walks up from a relative path now: path.dirname(".") is ., so a gate called with path: "." from a subdirectory stopped where it began and never reached the repository's .otitorc.json.
  • The CLI gates read the config of the repository they gate, not of the directory they run in. otito pass <repo>, otito review <repo>, otito gate and otito pass-pr <n> --path <repo> filled an omitted --policy or --governance from the .otitorc.json found from the working directory. On 2026-09-26, run from outside this repository, otito pass-pr 214 --path <checkout> gated #214 under team governance and failed it ("CODEOWNERS approval is missing for one or more changed files."), while otito pass-pr 214 run inside it read the checked-in "governance": "solo" and warned; run from inside it, otito pass <repo> gated a repository with no config as solo. Each command now resolves both from the repository it gates, the positional for pass and review, --path for pass-pr and either for gate, through gatePolicy in src/lib/config.js, which the MCP gate tools now share: that repository's .otitorc.json, then the user config. An explicit flag still wins and a blank one (--governance=) counts as omitted, as it does for the MCP tools. From outside the checkout, otito pass-pr 214 --path <checkout>, otito gate --pr 214 --path <checkout> and otito review <checkout> --pr 214 now report governance: solo, CODEOWNERS WARN and verdict WARN, and --governance team still fails the PR. otito workspace-gate gates every repository under one policy and one governance, which its parent receipt records once, so it now resolves each repository's own config and, when they disagree, refuses rather than gate one repository under another's setting: "workspace-gate runs every repository under one governance, and their configs disagree (…/web: team, …/api: solo); pass --governance to choose it". The flag settles it for all of them; repositories that agree, including through a shared .otitorc.json above them, need none. Emoji, color and theme still come from the directory otito runs in.
  • otito gate gates the repository it is given, in either mode. The local gate read the repository only from the positional and the PR gate only from --path; each ignored the other without a word and gated the directory otito ran in. On 2026-09-26, run from this checkout, otito gate --path <other-repo> --base HEAD~1 --json reported repo.root as this checkout, and otito gate <other-repo> --pr 214 --json gated #214, the PR with that number in this repository, under this checkout's .otitorc.json. The docs name the repository with --path (otito gate --pr "$PR_NUMBER" --path .), otito help with the positional (otito gate <repo>), and the agent-tools catalog with --path in both modes (otito gate [--pr <selector>] --path <repo>), so each documented form was silently dropped in one mode. Both modes now take the repository from the positional or --path, and resolve policy and governance from that repository's .otitorc.json. Naming it both ways is refused unless both name the same directory, compared by real path so . and a symlinked spelling of the same checkout agree: "gate was given two repositories (. and --path ../api); pass only one". A --path with no value is refused too. Rerun from this checkout, otito gate --path <other-repo> gates that repository under its own high-risk policy, otito gate <other-repo> --pr 214 asks gh about that repository, which has no remote ("no git remotes found"), instead of gating #214, and otito gate --pr 214 --path . still gates #214. otito help lists otito gate [repo | --path repo] for the local gate and otito gate --pr <selector> [repo | --path repo] for the PR gate.
  • otito review reviews the repository named by --path. review read the repository only from its first positional and took the positionals after it as the request, so it ignored --path, which the agent-tools catalog gives as the CLI form of the review_verdict MCP tool (otito review --path <repo> --json). On 2026-09-26, run from this checkout, otito review --path <other-repo> --base HEAD~1 --json reported repo.root as this checkout and gated it under this checkout's settings (the default standard policy), not the other repository's .otitorc.json (high-risk); otito review --path <other-repo> "tweak a" took the request for the repository ("repo path does not exist: …/tweak a"); and, run from another repository, otito review --pr 219 --path <checkout> asked gh about the repository it ran in ("no git remotes found") instead of #219. review now reads its arguments as impact and ax do: with --path, that names the repository and every positional is the request; without it, the first positional names the repository and the rest is the request. Policy and governance come from the .otitorc.json of the repository reviewed, and a --path with no value is refused: "review --path needs a repository, e.g. otito review --path .". Rerun, the first two review the other repository under its high-risk policy, the second with the request "tweak a", and the third reviews #219 under this checkout's solo governance (WARN). otito help lists otito review [repo | --path repo] [request].
  • otito help no longer says the legacy MCP tool names stop working at 3.0, and links a mapping that exists. It said pr_review, review_pr, merge_readiness, pr_merge_readiness, repo_catalog, repo_discover and the find_* tools "keep working via tools/call until 3.0. See docs/MIGRATION-2.0.md.", and the comment on LEGACY_TOOL_ALIASES in src/lib/mcp.js made the same promise. The deadline was Repoctx's, set when its 2.0 folded 18 tools into 11. The rebrand to otito (a0f4f98, 2026-07-29) deleted docs/MIGRATION-2.0.md, and docs/MIGRATION-3.0.md with it, and its renaming turned the comment's "until repoctx 3.0" into "until otito 3.0". otito 3.0.0 and 3.1.0 shipped with all ten names still dispatching, as tests/mcp-dispatch.test.js checks, and with the help unchanged, so it described a removal that never happened and linked a page that did not exist. The help and the comment now say the names still work in 3.x and that no release is named to remove them. Both point at a new "Legacy tool names" table under "MCP Tool Surface" in docs/02-mcp-agent-workflows/README.md, restored from the deleted guide, which maps each name to its canonical tool and says how its arguments are translated; the help links it on the docs site. The help test now fails if that section disappears, and a new test fails if the table and LEGACY_TOOL_ALIASES disagree on a name or its target. No alias was removed.
  • The MCP gate tools read a blank pr as no PR, and pr_merge_readiness with no selector gates the checked-out branch's PR again. Before 2.0, pr_merge_readiness gated "the current branch's PR" when its selector was left out, as its schema said and as gh pr view does. #66 folded it into review_gate and mapped a missing selector to pr: "", which review_gate reads as no PR, so the alias has run the local gate ever since, while the comment in review_gate said it did "exactly what the old pr_merge_readiness and merge_readiness did". review_verdict read pr differently again: "" ran the local gate, but " " counted as a PR, and gh pr view drops a blank selector and answers with the checked-out branch's PR. On 2026-09-26, against a checkout of fix/review-path-flag, pr_merge_readiness {} and review_gate { pr: " " } each returned a local-gate report against origin/main, while review_verdict { pr: " " } gated #220, which it never named ("PR is already merged; there is nothing left to gate."). The test meant to pin the route, "empty pr selector still routes to the PR gate and returns a payload", checked only that some text came back, so it passed on the local gate. review_gate and review_verdict now read a blank pr as omitted and run the local gate, as they already read a blank policy or governance: unlike --pr "$PR_NUMBER" on the command line, a JSON argument has no unset variable behind it. A pr that names a PR still gates it. pr_merge_readiness with no selector, or a blank one, gates the checked-out branch's PR, which review_gate's own pr cannot ask for; a selector, or a pr, still names the PR. Both tools' pr schema descriptions say so, and the tests run each call against a fake gh that answers pr view 42 and a bare pr view with different PRs, and assert which one, if either, was gated. Rerun, pr_merge_readiness {} gates #220 and review_verdict { pr: " " } runs the local gate.
  • otito gate --pr and otito review --pr refuse a --pr that names no PR, and otito pass-pr a blank selector. The gate switched to the GitHub PR gate only when --pr carried a non-empty value. On 2026-09-26, otito gate --path . --base HEAD~1 --pr= --json and the same command with a bare --pr each returned a local-gate report, verdict WARN, exit 0 and no pr field, as if --pr had never been given. The documented CI form otito gate --pr "$PR_NUMBER" --path . fared worse with the variable unset: the parser reads --pr "" as a bare --pr and keeps the empty string as a positional, which the gate took for the repository, failing with "git rev-parse --show-toplevel: command failed" when run from the repository and "gate was given two repositories ( and --path …); pass only one" from anywhere else. otito review --pr= ran the local gate the same way, and otito review . --pr "$PR_NUMBER" with the variable unset asked gh for the PR of a branch named true. A blank selector that reached gh was dropped, so gh pr view fell back to the checked-out branch's PR: against a checkout of fix/cli-gate-config-from-gated-repo, otito pass-pr "" --path <checkout> and otito gate --pr " " --path <checkout> both gated #217, which neither named. gate and review now refuse a --pr that is bare or blank before they read a repository, "gate --pr needs a PR number or URL, e.g. otito gate --pr 123 --path ." (review names otito review . --pr 123), and pass-pr refuses a blank selector: "pass-pr was given a blank PR selector; name the PR, e.g. otito pass-pr 123 --path ., or leave it out to gate the current branch's PR". Each exits 1, with the message as error under --json. Leaving pass-pr's selector out still gates the current branch's PR, as gh pr view does, and otito gate --pr 217 --path . still gates #217.