v2.1.0
v2.1.0 adds one new MCP tool (branch_guard) and hardens several existing ones against silent false-positive gate results — the dominant theme this cycle is "a compliance/trust check returned a confident ok:true/success when it shouldn't have." ci_wait_run gains a merge-result anchor to stop grading the wrong pipeline; ibm and branch_guard stop guessing repo/branch pairings; work_item stops leaking type::epic onto Plans. Also: three wave-engine handlers stop hardcoding main as the mainline, a deploy-freshness startup check was added, and two HIGH CVEs were patched. CHANGELOG.md was retired in this range (#459) — GitHub Releases notes (this document) are now the authoritative changelog; there is no in-repo file to check.
New tools
branch_guard(#469, refined #471, #485) — Guards against basing on / merging into the wrong mainline. Input:role('base'|'target'), optionalbranch, optionalrepo. Resolves the git host's live default branch and verdicts whether the checked branch is "protected" by name (mainorrelease/*— a shared pattern withibm, not a live host protection query as originally shipped in #469 and reworked in #471). Returns{verdict: 'pass'|'warn', reason, default_branch, checked_branch, is_protected, is_sandbox};kahuna/*sandboxes always pass. Warns when a protected-by-name branch is neither the live default nor a sandbox (e.g. a stalerelease/0.0.1after the default moved torelease/1.0.0). Call it before basing/merging to catch drift against a moved default branch. As of #485,role:'base'with an explicitrepobut nobranchis refused rather than pairing the server cwd's current branch with a possibly-unrelated foreign repo.
Changed
ci_wait_run(#478, #484) — New optional paramsrequire_merge_result: booleanandpr_number: number(required together via a zod.refine). Setrequire_merge_result: trueto accept only a run that validated the actual merge result for the PR/MR's current head — GitHub: apull_requestrun whosehead_shamatches the PR head; GitLab: the pipeline GitLab reports as the MR'shead_pipeline(a detached/heador merge-train/trainpipeline no longer counts). A non-qualifying run now yieldsfinal_status: "not_merge_result"instead of a misleading success. Also: the "no run has appeared yet" grace window is now decoupled by call shape —require_merge_resultstill gets the fulltimeout_sec(merged-results pipelines are slow to appear), but a plainexpected_shawait is now bounded tomin(timeout_sec, 180s)instead of silently consuming the entire timeout on a transient first-poll miss. Phase-2 (polling a run once it has appeared) is unaffected either way.ibm(#481) — New optionalrepoparam, threaded through both the issue lookup and the PR/MR lookup. Pass it whenever the branch being checked is not the one checked out in the server's cwd. Behavior change: an unrecognized branch with norepogiven is now refused (ok:false) rather than silently resolved against the cwd's repo — previously this could match a same-numbered but unrelated issue in the wrong repository and return a falseok:truecompliance pass. The response envelope now echoes therepoactually checked.work_item(#479) —typeenum gains'plan'(appliestype::plan); previously/issue planwas documented but not creatable. Behavior change: the autotype::<type>label is now suppressed whenever the caller already supplies anytype::*label (both platforms) — previously it was always prepended, which was only safe on GitLab by accident (scoped-label eviction); on GitHub the same call could produce an issue tagged with bothtype::epicandtype::plan. Caller-supplied labels are also now trimmed/normalized once before both the suppression check and the platform argv, and a GitHub--labelfailure on a non-existent label now names the remedy (label_create) in the error message.wave_init— thekahunabootstrap's implicitbase_branchno longer defaults to the literal'main'; when the plan omitsbase_branchit now resolves the repo's live default branch via the adapter (#473).wave_finalize—target_branchchanged fromz.string().min(1).default('main')toz.string().min(1).optional(). An explicit value still wins; when omitted, it now resolves the repo's live default branch instead of silently targetingmain(#473). Matters for any repo whose default branch isn'tmain(e.g.release/1.0.0).- Internal-only, no signature/output change:
fetch-ci-trust-signal-github's branch-protection probe (backswave_ci_trust_level) now probes the live default branch instead of a hardcodedmain(#473). FlightDeck event emission was wired intocommutativity_verify,drift_check_path_exists,drift_check_symbol_exists,drift_files_changed,ci_wait_run,pr_wait_ci,wave_ci_trust_level,pr_merge, andwave_finalize(#464) — fire-and-forget telemetry to~/.claude/status/events.jsonl/FlightDeck ingest; every emit runs after the handler's normal work and never alters the response or control flow. - Server startup —
SERVER_VERSIONnow derives from the injected build tag (git describe, e.g.2.1.0) instead of a hardcoded'1.0.0'; uncompiled dev runs report0.0.0-dev. A new one-time, non-blockingdeploy_freshnesscheck compares the running binary's embedded build SHA against the server's own latest GitHub release and emits a singlewarn-level log line if the binary is stale (#482, closes #447) — network-optional, degrades silently ifgh/network/releases are unavailable, and answersinitializein ~150ms even while the freshness check is in flight.
Fixed
ci_wait_run/ wave trust gate (#478) — Fixed a live false-pass: GitLab skips the branch pipeline on a merge commit, and the status mapping treatedskippedassuccess, so a kahuna merge commit's gate could reportfinal_status: "success"for a pipeline that never ran. Also closed a "freshness by absence" gap where a green merge-result run for a previous commit could satisfy the gate for a PR/MR whose head had since moved — the run is now re-validated against the anchor before grading.ci_wait_run(#484) — Fixed a silent full-timeout_sechang: withexpected_shaset, a transient miss on the first CI-run poll used to consume the entire (typically 1800s) timeout with zero partial output before failing; now bounded to ~180s with an actionable error.ibm(#481) — Fixed a false compliance pass where a branch not checked out in the server's cwd got its issue number looked up against the cwd's repo, matching an unrelated same-numbered issue in the wrong repository.work_item(#479) — Fixed a GitHub-only taxonomy leak where an issue created withtype: 'epic'plus a caller-suppliedtype::planlabel ended up carrying both labels (GitHub has no scoped-label mutual exclusion, unlike GitLab).branch_guard(#480, shipped in #485) — Fixed the same cwd-branch/foreign-repo pairing trap as theibmfix, scoped torole:'base': arepogiven without abranchno longer infers the cwd's current branch and resolves it against a repository it may not belong to.- Security (#468, #450, shipped in #485) —
fast-uribumped to^3.1.2(resolves 3.1.3) andhonoto^4.12.25(resolves 4.12.30), patching CVE-2026-6321, CVE-2026-6322, and CVE-2026-54290.trivy fs --severity HIGH,CRITICAL: 0 findings.