Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: commit-check/commit-check
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v2.13.1
Choose a base ref
...
head repository: commit-check/commit-check
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v2.13.2
Choose a head ref
  • 4 commits
  • 8 files changed
  • 2 contributors

Commits on Aug 5, 2026

  1. chore: expand imperative verb whitelist and improve validation logic (#…

    …527)
    
    * fix: recognise 133 more imperative verbs
    
    CC003 matches the subject's first word against a fixed list, so a verb missing
    from it rejects a subject that was written correctly. The contributor then has
    to reword something that was never wrong, and the only way to learn which words
    are acceptable is trial and error.
    
    Measured against 40,000 git.git subjects — a project that writes strictly
    imperative subjects, with its area prefixes stripped so only the verb is
    compared — the list rejected 17.9% of them. This brings that to 10.5%.
    
    The additions come from that corpus, filtered by hand to unambiguous verbs:
    avoid, clarify, teach, factor, free, mention, plug, inline, restore, retire,
    treat, tighten and the rest. Adverb-led subjects ("always quote the path") and
    non-verbs were left out — the first are correct English the list cannot
    represent, and the second would weaken the check.
    
    Both spellings of every -ize/-ise verb are now present. The file had sixteen
    -ize verbs and four -ise ones, so a project writing British English was being
    told "normalise" is not imperative.
    
    Wrong forms still fail, which is the property that matters: "updated the
    parser", "adding a new flag", "fixes the crash" and "removed the dead code" are
    covered by a test alongside the recovered words.
    
    The remaining 10.5% is the point of the follow-up issue: a list can only ever
    approximate "is this an English imperative verb".
    
    Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01U9zFxq8V4qxG4aMzJhGBFn
    
    * test: keep the new imperative cases out of the benchmark suite
    
    They assert which subjects are accepted, not how fast the check runs, and
    marking them adds twelve identical-cost cases to what CodSpeed measures. The
    companion test for wrong verb forms was already unmarked, so the pair was
    inconsistent as well.
    
    Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01U9zFxq8V4qxG4aMzJhGBFn
    
    * test: assert a spelling pair this change actually completed
    
    The British-spelling case used serialise, which the file already contained
    before this change, so it proved nothing about the addition. customise is the
    useful one: customize was listed and customise was not, which is exactly the
    half-a-pair bug the sweep was meant to close. Both spellings are now asserted.
    
    Reported by CodeRabbit on #527, and correct — verified against the merge base
    that serialise predates the change while customise does not.
    
    Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01U9zFxq8V4qxG4aMzJhGBFn
    
    ---------
    
    Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
    shenxianpeng and claude authored Aug 5, 2026
    Configuration menu
    Copy the full SHA
    1072b87 View commit details
    Browse the repository at this point in the history

Commits on Aug 6, 2026

  1. fix: print the display name in compact output, not the config key (#529)

    * fix: print the display name in compact output, not the config key
    
    The same failing check is named two ways depending on the format:
    
        $ commit-check -m --subject-imperative=true --no-banner
        CC003 subject-imperative check failed ==> docs: revamped the profile
    
        $ commit-check -m --subject-imperative=true --compact
        [FAIL] CC003 subject_imperative: docs: revamped the profile
    
    Both are text written for a person, so they should agree. The kebab-case
    form is the one that agrees with the rules reference, whose headings read
    `subject-imperative (CC003)` — so a name printed to a terminal can be
    searched for there verbatim, which is why the default output already
    converts.
    
    Every other human-facing surface in the ecosystem does the same:
    commit-check-action converts the JSON `check` field before showing it in
    step logs, job summaries and pull request comments. Compact was the only
    one left printing the raw key.
    
    The JSON output is deliberately unchanged. Its `check` field carries the
    snake_case key so a consumer can map a failure back to the `cchk.toml`
    option that controls it, which is what the action relies on.
    
    Both call sites now go through one helper rather than one of them
    open-coding the conversion, which is how they drifted apart to begin
    with.
    
    Refs #528
    
    * refactor: keep one implementation of the check display name
    
    Review pointed out that RuleCatalogEntry.name already did the same
    snake-to-kebab conversion, so the helper added in the previous commit was
    a second copy and its docstring claim that every text surface goes
    through it was false. Two copies of the rule this change exists to
    enforce is the wrong number.
    
    The helper moves to rules_catalog rather than the catalog importing it
    from util. rules_catalog is a pure data module — dataclasses and nothing
    else — and commit-check-mcp imports it directly, so it should not gain a
    dependency on a module that pulls in os, sys and subprocess. Pointing the
    arrow the other way keeps the catalog light and puts the formatter beside
    the entry whose name property defines the concept.
    
    Verified there is now one implementation, that both call sites reach it,
    and that importing util, rules_catalog, engine and main together raises
    no cycle.
    shenxianpeng authored Aug 6, 2026
    Configuration menu
    Copy the full SHA
    7315edf View commit details
    Browse the repository at this point in the history
  2. docs: refresh the demo recording, and fix a tape that recorded a fals…

    …e pass (#530)
    
    * docs: refresh the demo recording, and fix a tape that recorded a false pass
    
    The GIF predates rule IDs, so it showed neither the CC001/CC201 identifiers
    nor the docs links that every failure now prints.
    
    Three things were wrong with the tape itself, all found by watching the
    re-recording rather than by reading it:
    
    The hidden setup never configured a git identity, so the initial commit
    failed, the repository was left with no commits at all, and every branch
    check then passed silently. The old recording showed `commit-check -b` on
    `user-login` producing no output whatsoever — a failing branch recorded as
    clean.
    
    It recorded in whatever directory it was invoked from, so running it left
    `user-login` and `feature/user-login` behind in the maintainer's working
    copy. It now records inside a throwaway repository.
    
    The terminal was too narrow for the output, breaking the conventionalcommits
    URL mid-token. The width is now measured rather than assumed: `tput cols` in
    the recorder gives 132 columns at 1400px, and the longest line CC001 prints
    is 131. The glyph advance is 10.26px, not the 9.6px that assuming 0.6em
    gives you.
    
    Two smaller changes: a passing check prints nothing and exits 0, so the two
    valid scenes reported an empty screen for four seconds — they now echo the
    result. And `Output` pointed at demo.gif while the header said to run the
    tape from the repository root, which wrote the file to the wrong directory.
    
    CC201's suggestion is 161 columns and still wraps; fitting it would need a
    1700px terminal, which is not a demo, it is a tell that the string is too
    long.
    
    Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01U9zFxq8V4qxG4aMzJhGBFn
    
    * docs: stop the demo tape from falling back to $HOME, and clean up after it
    
    When a command substitution produces nothing, zsh reads `cd $(...)` as a
    bare `cd` and goes to $HOME. Confirmed rather than assumed:
    
        $ zsh -c 'cd $(true); echo $PWD'
        /root
    
    So a failing mktemp would have left the tape running `git init` in a
    maintainer's home directory. DEMO_REPO is now guarded with ${DEMO_REPO:?},
    which aborts the chain instead:
    
        $ zsh -c 'DEMO_REPO=$(false) && cd ${DEMO_REPO:?} && echo REACHED'
        (chain stops, cwd unchanged)
    
    The same guard covers the new hidden cleanup, so rm -rf can never be handed
    a bare path. A full run now leaves zero directories behind under /tmp,
    checked from a clean slate.
    
    Re-recorded so the committed GIF is the one this tape produces. The visible
    frames are unchanged — both new commands are inside Hide blocks.
    
    Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01U9zFxq8V4qxG4aMzJhGBFn
    
    ---------
    
    Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
    shenxianpeng and claude authored Aug 6, 2026
    Configuration menu
    Copy the full SHA
    e2edc16 View commit details
    Browse the repository at this point in the history

Commits on Aug 7, 2026

  1. fix: resolve merge-base refs that exist only on the remote (#532)

    * fix: resolve merge-base refs that exist only on the remote
    
    CC202 reported "not rebased onto target branch" for a branch that was
    correctly based on main the whole time — in every CI checkout of a pull
    request. Two halves, same mistake: exit 128 from git means "could not
    resolve that name", and both call sites read it as "not an ancestor".
    
    _find_target_branch verifies refs/heads/main, falls back to verifying
    refs/remotes/origin/main, then returns the bare name either way. A pull
    request checkout has only the remote-tracking ref, so the caller ran
    
        git merge-base --is-ancestor main HEAD
        fatal: Not a valid object name main   (exit 128)
    
    and the failure was reported as a rebase problem. The remote fallback now
    returns origin/<name> — the ref that was just verified. Note that writing
    require_rebase_target = "origin/main" in config is not a workaround:
    _find_target_branch tries refs/heads/origin/main and
    refs/remotes/origin/origin/main, finds neither, returns None, and the
    check silently passes without checking anything.
    
    The second half: get_branch_name() falls back to GITHUB_HEAD_REF, so a
    detached CI checkout reports a branch name that exists on no local ref.
    Same 128, same misreading. validate() now retries against HEAD — the same
    commit, always resolvable — and only a real non-zero ancestry answer
    fails.
    
    Measured in a clone shaped like the runner's checkout:
    
        _find_target_branch('main')           -> 'main'      (before fix)
        git_merge_base('main', 'HEAD')        -> 128
        git_merge_base('origin/main', 'HEAD') -> 0
    
    The existing tests never caught this because none of them ran the code
    they named: two patched commit_check.util.git_merge_base while the engine
    imports the name directly, so the mock never bound and real git ran
    against whatever checkout pytest was in — one of them passed only because
    128 was misread as FAIL. A third built its rule with no regex, so
    validate() returned PASS before reaching the mocked call (call count: 0).
    All three now assert against the engine's own reference, and two new
    tests drive real git in pull-request-shaped clones. Reverting either fix
    fails its test.
    
    Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01U9zFxq8V4qxG4aMzJhGBFn
    
    * ci: run the checks again after a runner provisioning failure
    
    The build job died before reaching any code: GitHub's runner could not
    download its own actions ("Failed to resolve action download info.
    Error: Service Unavailable", three attempts). The workflow token is
    read-only, so a re-run cannot be requested through the API — an empty
    commit re-triggers everything and disappears in the squash merge.
    
    Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01U9zFxq8V4qxG4aMzJhGBFn
    
    * fix: check the remote branch before HEAD when resolving merge-base
    
    Re-review caught this before merge: the HEAD fallback traded the false
    failure for a false pass. On a pull_request event the runner checks out
    GitHub's synthetic merge commit, whose first parent IS the target tip —
    so is-ancestor(target, HEAD) is true by construction, for every branch,
    rebased or not. Measured on a diverged branch in that shape:
    
        git_merge_base('origin/main', 'feat/work')        -> 128
        git_merge_base('origin/main', 'HEAD')             -> 0   <- wrong
        git_merge_base('origin/main', 'origin/feat/work') -> 1   <- the truth
    
    An unresolvable branch name now resolves through its remote-tracking ref
    first; HEAD remains only as the last resort, where it still gives a real
    answer on checkouts whose HEAD is the branch commit itself (push events,
    or a branch that was never pushed). A new test builds the merge-ref shape
    with a genuinely diverged branch and asserts FAIL — disabling the
    origin/<branch> step fails it.
    
    Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01U9zFxq8V4qxG4aMzJhGBFn
    
    * chore: retrigger checks after a stuck workflow queue
    
    The CodSpeed run for 47cd595 has sat in "queued" for nine hours after
    yesterday's GitHub Actions incident and can no longer be cancelled
    ("Cannot cancel a workflow re-run that has not yet queued"), so its
    check never reports. CodeQL's Analyze (python) on the same SHA cannot
    be re-run through the API either — it answers 403 "cannot be retried".
    
    An empty commit is the only lever that reaches both: a new head SHA
    starts fresh check runs for the whole suite. No file changes.
    
    Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01U9zFxq8V4qxG4aMzJhGBFn
    
    * fix: resolve the pull request head when merge-base falls back to HEAD
    
    Review caught a residual false pass in the fallback chain. When the
    branch is unresolvable under both its own name and origin/<branch>,
    the last resort asked about HEAD -- but on a pull_request event HEAD
    is GitHub's synthetic merge commit, whose first parent IS the target
    tip, so it passes any branch. Measured in that shape with the remote
    ref removed, on a branch that is genuinely behind:
    
        git_merge_base('origin/main', 'feat/work')        -> 128
        git_merge_base('origin/main', 'origin/feat/work') -> 128
        git_merge_base('origin/main', 'HEAD')             -> 0   <- wrong
        git_merge_base('origin/main', 'HEAD^2')           -> 1   <- the truth
    
    HEAD's second parent is the pull request head, the commit actually
    under review, so the fallback now asks about that whenever HEAD is a
    merge. This answers rather than giving up: where HEAD has a single
    parent it is the branch commit itself and still answers for itself,
    so the rebased detached-checkout case keeps passing.
    
    Adds git_rev_parse_verify to test for the second parent, and a test
    that builds the merge-ref shape with no remote ref and asserts FAIL --
    restoring the plain HEAD fallback fails it.
    
    Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01U9zFxq8V4qxG4aMzJhGBFn
    
    * test: share the merge-ref setup between the diverged cases
    
    SonarCloud failed the quality gate at 14.2% duplication on new code
    (limit 3%). The two diverged-branch tests repeated the same twenty
    lines of setup and the same chdir/patch/validate dance.
    
    Extracts _diverged_merge_ref_clone for the shape and
    _validate_merge_base for the invocation, leaving each test as its
    distinguishing step plus an assertion. Net 13 lines lighter, and the
    regression test still fails when the plain HEAD fallback is restored.
    
    Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01U9zFxq8V4qxG4aMzJhGBFn
    
    ---------
    
    Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
    shenxianpeng and claude authored Aug 7, 2026
    Configuration menu
    Copy the full SHA
    98fb97c View commit details
    Browse the repository at this point in the history
Loading