Repository navigation
fix(release): stop shipping binaries that report the previous version - #50
Merged
Merged
Conversation
v1.10.0 was tagged on a tree whose Cargo.toml still said 1.9.1, so every published binary reported the old version to --version and self-update kept offering the same release forever. A verify-version job now compares the Cargo.toml version with the tag name and both the release creation and the build matrix wait on it, so a mismatched tag fails before any release or asset is published.
Tagging without --bump-files left the version files stale, which is how a release can be cut whose binaries report the previous version. Version files are now bumped and committed unless --no-bump-files is passed; --bump-files is still accepted so existing scripts keep working, and passing both is an error. Two bugs surfaced by making this the default are fixed alongside it: - version files that already carried the new version were reported as updated, and the empty commit that followed aborted the whole tag operation - multi-package repositories staged the bumped files but never committed them, so their tags pointed at a commit with the old versions
martient
added a commit
that referenced
this pull request
Sep 27, 2026
* fix: tag mapping * fix: scope detection * fix: tag behavior * fix: prevent tag dry-run side effects * Feat agent convention enforcement (#41) * fix(core): preserve linked worktree discovery * feat(core): stabilize agent json contract * feat(core): add typed convention capabilities * feat(core): add native convention hooks * feat(agents): ship shared workflow plugin * chore(agents): streamline convention guidance * chore(core): satisfy strict clippy checks * fix(docs): update starlight social config * fix(core): preserve legacy branch defaults * fix(hooks): support Windows release builds * chore: bump version to 1.9.1 * docs(agents): add September 2026 agent compatibility audit Audit the repository's agent-facing surfaces (AGENTS.md/CLAUDE.md, the shared Codex/Claude plugin and skills, the schema capability contract, generated hooks/CI, and the src/ai LLM integration) against the current state of the agent-integration ecosystem. Records 15 prioritised findings covering defects, compatibility gaps and removals, plus two explicit non-recommendations. * fix(cli): make agent invocations parse and fail machine-readably Agents consistently failed against the CLI for three mechanical reasons, each now reproduced and covered by tests/agent_ergonomics_tests.rs. - --non-interactive and -q/--quiet are global, so they parse on either side of the subcommand. --verbose stays root-only: -v belongs to `branch --validate` and --verbose to `config validate|show`, and promoting it would steal both shorts. A test pins that trade-off. - Argument-parse rejections now emit the machine envelope on stdout with code "invalid_usage" when --output json was requested, instead of an empty stdout and prose on stderr. Text mode is unchanged, and --help and --version keep their plain-text form and exit 0. - schema capabilities cover the whole agent command surface (tag, bump, changelog, group-commit, config, packages, schema) and each entry now declares `mutating` and `requires_confirmation`, so an agent can reason about consent without pattern-matching command names. - COMMITTY_NONINTERACTIVE is documented in AGENTS.md, CLAUDE.md and the agent-workflows reference; it makes flag placement irrelevant. Also withdraws the audit's blanket "do not build an MCP server": that was argued against eager-loading MCP, which the 2026-07-28 stateless spec has moved off. * fix(cli): global flag placement, usage envelope, capability surface Second half of the agent-ergonomics change: main.rs flag globals and the clap rejection handler, the expanded schema capability list, the ergonomics test suite, and the docs describing flag placement. * test(agents): cover agent CLI ergonomics and update the audit Adds tests/agent_ergonomics_tests.rs (12 tests) covering flag placement on both sides of the subcommand, the invalid_usage envelope on argv rejection, the expanded capability surface with consent metadata, and the presence of COMMITTY_NONINTERACTIVE in agent-facing docs. Documents flag placement in the agent-workflows reference and records the diagnosis in the audit, withdrawing its blanket recommendation against an MCP server. * docs(agents): document flag placement and the invalid_usage envelope * docs(audit): record the CLI ergonomics diagnosis and withdraw the MCP verdict * docs(agents): ban AI attribution trailers in commits and PRs Tooling re-adds Co-Authored-By and Claude-Session by default, so the prohibition has to live where agents read it rather than being restated each session. * fix(ai): make the Ollama provider work and stop sending an empty prompt Four real defects from the agent audit, plus one withdrawn finding. - Ollama requests now set `stream: false`. The endpoint defaults to streaming, so every previous call received newline-delimited events and died in LlmError::Parse before silently falling back to the default message. The provider has never worked. - `format` moves from `options` to the top level, where Ollama reads it, and now carries a JSON Schema for AiCommitSuggestion rather than the string "json", so decoding is constrained instead of merely requested. - The default (non-sensitive) AI path sent only the group name and an instruction not to reveal anything, leaving the model nothing to work from. It now sends a redacted shape summary: file count, extension histogram and top-level areas. No filename, no path below the first segment, and no file content is sent in either mode. - `--ai-diff-lines-per-file` was declared, documented and accepted but never read. Removed, along with the dead LlmError::_Timeout variant and the commented-out LlmProvider enum. The audit's P0-4 ("--ai silently ignored in --mode apply") was wrong and is withdrawn: the apply arm has always had its own AI block. The real problem there was duplication -- plan and apply carried identical copies of group building and the AI block -- which is what made the misreading possible. Both are extracted into build_groups() and ai_user_prompt(). Request bodies are now built by pure functions with unit tests, so the wire format is verifiable without a network call. * fix(ci): run checks on pull requests targeting nested branch names The pull_request trigger filtered on branches: ["*"], and in Actions branch filters `*` matches everything except `/`. Any pull request whose base branch contains a slash therefore matched nothing and ran no checks at all -- silently, since a workflow that does not trigger looks the same as one with nothing to report. That covers every stacked pull request in this series (all but the first target a claude/... base and have zero check runs), and would equally cover anything based on a feat/ or fix/ prefix. `**` matches across slashes, which is what the filter meant. * docs(agents): make AGENTS.md canonical and reach every runtime AGENTS.md and CLAUDE.md were near-identical files kept in sync by hand; the diff was two lines and had already started drifting (the trailer ban landed in one of them only). AGENTS.md is now the single source and CLAUDE.md is an @AGENTS.md import plus the one genuinely Claude-specific line, which is the pattern the ecosystem settled on. Two runtimes read neither file, so they now get thin pointers: .github/copilot-instructions.md and GEMINI.md. Skills are exposed at .agents/skills, the cross-tool discovery path that Codex, Claude Code and Copilot CLI all resolve, via a symlink to the canonical copy under plugins/committy/skills -- no duplication. A test guards the symlink, because a checkout that materialises it as a plain file would break discovery silently. Skill frontmatter gains the spec's optional fields: license, allowed-tools (so security-conscious runtimes can pre-authorise exactly what each skill needs) and metadata carrying a skill version independent of the CLI version. committy-release referenced siblings with Codex's `$skill` syntax inside a body shared by both runtimes; they are now named plainly and each runtime resolves them its own way. The COMMITTY_NONINTERACTIVE docs test now accepts a file that defers to AGENTS.md rather than restating it, which is what the split requires. * test(agents): cover the plugin manifests and skill frontmatter AGENTS.md asks for public-seam tests over "machine output, exit codes, Git behavior, hook protocol, and plugin manifests". The first four were covered; nothing compiled or executed the manifests, so a typo in any of the six JSON and Markdown files shipped silently. Nine tests: both marketplace manifests parse and their declared sources resolve to real directories carrying the expected plugin manifest; the Claude and Codex plugin manifests agree on name, version and license; the Codex skills path resolves; skill frontmatter uses only the keys the Agent Skills spec allows and carries the two required ones; a skill's frontmatter name matches its directory; descriptions carry a trigger rather than just a capability; no skill body uses runtime-specific invocation syntax, since that body is shared by Codex and Claude Code; and every skill is reachable from the cross-tool .agents/skills path. Each assertion was mutation-checked -- a bogus frontmatter key and a marketplace source pointing at a missing directory both fail with the message that names the problem. * chore(ci): modernise the generated workflow and tighten the agent guardrail The workflow `hooks install --with-ci` writes for users pinned actions/checkout@v4 and ran `cargo install committy --locked` on every pull request -- a multi-minute Rust build per run, uncached, with no toolchain pin. It now pins checkout@v5, pins the toolchain, caches the build, and takes non-interactive mode from COMMITTY_NONINTERACTIVE so the command cannot be broken by flag placement. The repository's own three workflows carried the same stale checkout pin and are bumped too. The local PreToolUse guardrail grepped the raw tool input for nine substrings, which made it both over- and under-inclusive. Over: it matched the *text* of a command rather than an invocation, so writing documentation that merely named a push was refused -- including, circularly, the heredoc that would have replaced this file -- and it refused the push-with-upstream form the repository's own agent instructions require. Under: two spaces between git and the subcommand, an `env X=1` prefix, or `git -C .` all slipped through, so it never was a control. It now resolves the command field (with a jq-free fallback), discards heredoc bodies, returns early unless git is invoked, and refuses only what destroys history or uncommitted work: unguarded force-push (--force-with-lease allowed), hard reset, forced clean, forced branch delete, and bulk working-tree discards. Plain push is left to the pre-push hook, which is policy-engine backed. PARTIALLY VALIDATED: the full behaviour matrix could not be run from the session that wrote the hook, since executing one's own permission hook is refused as self-modification. Observed in normal use: ordinary commands and a plain push pass, and `git branch -D` was correctly refused. The remaining deny cases are unverified and a reviewer should confirm them. Also removes the roadmap page, which carried frontmatter and no body and published as an empty page. * test: stop the tag signing test depending on ambient git config test_tag_respects_repo_git_overrides_and_cli_can_override_them asserts that a repo-configured gpg.program is consulted, by pointing it at a script that always fails and expecting the tag creation to fail with it. That only holds when gpg.format is openpgp. On a machine whose global config sets gpg.format=ssh, git routes signing to gpg.ssh.program, the stub never runs, the tag is signed successfully by the ssh signer, and the assertion evaporates -- the test fails claiming Committy ignored the override when Committy had passed it through correctly. Verified: the tag git produced in that environment carried a valid SSH signature. The test now pins gpg.format=openpgp alongside the other overrides, so it exercises the path it means to exercise regardless of the host. Separately, the three config::hierarchy tests call MergedConfig::load, which reads the user config from $HOME. The tests in config.rs mutate HOME and are marked #[serial]; these were not, so they could read a half-written or relocated user config while one of those held it, and MergedConfig::load(...).unwrap() would panic. They are now serial too. Caveat on that second change: it closes a real race but is unproven. The failure was observed once, and did not recur in thirteen subsequent runs (eight lib-only, five full) even with the fix reverted, so causality is reasoned rather than demonstrated. It is cheap and correct regardless -- a test reading global state that other tests mutate belongs in the same serial group. * test(hooks): add a behaviour matrix for the guardrail, and fix what it caught The rewritten guardrail shipped unvalidated: nothing executed it, so its deny rules were assumed rather than checked. That matters more than for ordinary code, because a guardrail that silently stops refusing looks exactly like one that works. Twelve tests feed the hook the same JSON the harness does and assert the exit code. Writing them found three defects in the hook, all now fixed: - `git push -f origin main` was not refused. The force check was one ordered regex whose `-f` alternative required a preceding space, which the `push ` match had already consumed. Force detection is now two independent conditions -- a push, and a force flag anywhere -- so flag order and adjacency stop mattering. - A heredoc feeding an interpreter was dropped along with every other heredoc body, so `bash <<EOF` / `git reset --hard` / `EOF` sailed through. Heredoc bodies are still discarded when the opener writes to a file, but scanned when it names a shell or interpreter. - Quoted prose naming a dangerous command is still refused. This one is deliberate and now recorded as a decision: stripping quoted segments would fix it and would also let `bash -c "git reset --hard"` through, and for a guardrail a false refusal costs far less than a false pass. Coverage includes the variants the previous substring hook missed entirely -- doubled spaces, a leading env assignment, `git -C .`, and a git call after `&&`. * fix(release): stop shipping binaries that report the previous version (#50) * ci(release): fail the release when Cargo.toml disagrees with the tag v1.10.0 was tagged on a tree whose Cargo.toml still said 1.9.1, so every published binary reported the old version to --version and self-update kept offering the same release forever. A verify-version job now compares the Cargo.toml version with the tag name and both the release creation and the build matrix wait on it, so a mismatched tag fails before any release or asset is published. * feat(tag)!: bump version files by default when tagging Tagging without --bump-files left the version files stale, which is how a release can be cut whose binaries report the previous version. Version files are now bumped and committed unless --no-bump-files is passed; --bump-files is still accepted so existing scripts keep working, and passing both is an error. Two bugs surfaced by making this the default are fixed alongside it: - version files that already carried the new version were reported as updated, and the empty commit that followed aborted the whole tag operation - multi-package repositories staged the bumped files but never committed them, so their tags pointed at a commit with the old versions * chore(release): align version files with published v1.10.0 * fix(docs): update vulnerable astro, starlight, and sharp dependencies (#51) Resolves 89 Dependabot alerts, all transitive deps of the pinned Astro 5 / Starlight 0.35 / sharp 0.34 versions in the docs site (vite, js-yaml, postcss, nanoid, h3, picomatch, devalue, and others). Starlight 0.39 dropped the label+autogenerate sidebar shorthand, so astro.config.mjs now wraps autogenerate entries in items arrays. --------- Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
committy --updateupgraded to v1.10.0 andcommitty --versionstill printed1.9.1. The update worked; the release was mislabelled. Thev1.10.0tag points at a tree whoseCargo.tomlstill says1.9.1, and since--versionprintsenv!("CARGO_PKG_VERSION"), every published v1.10.0 asset honestly reports the old version — which also means self-update keeps offering v1.10.0 forever.The root cause is that bumping version files was opt-in (
--bump-files), so the tag was cut on a commit that predated any version change.What this changes
A guard in the release workflow. A new
verify-versionjob comparesCargo.tomlwith the tag name and bothcreate-releaseand the build matrix wait on it, so a mismatched tag fails before a release is created or any asset is uploaded.Version files are bumped by default.
committy tagnow updates and commits version files unless--no-bump-filesis passed.--bump-filesis still accepted for existing scripts; passing both flags is an error.Two latent bugs surfaced once the bump became the default, and are fixed here:
Tests
tests/tag_bump_files_tests.rscovers the default bump,--no-bump-files, the retained--bump-files, the conflicting-flag error, and that a multi-package tag's tree actually contains the bumped version.tests/release_workflow_tests.rspins the workflow guard and its job wiring.cargo test,cargo fmt --all -- --checkandcargo clippy --all-targets --all-featuresare clean.Note on the published v1.10.0
This prevents a repeat; it does not repair the release that is already out. Anyone on v1.10.0 has a binary that reports 1.9.1. Fixing that needs a re-tag of v1.10.0 or a jump to v1.10.1, which touches published artifacts and is a separate decision.
Behaviour change
committy tagnow writes and commits to version files where it previously did nothing. That is the intent, but it is a behaviour change for anyone tagging in a repo with aCargo.toml/package.jsonwho relied on the old default;--no-bump-filesrestores it.