Skip to content

feat(git): more accurate commit work types without a prefix - #143

Merged
bhavsarpratik merged 6 commits into
mainfrom
feat/git-commit-type-accuracy
Nov 6, 2026
Merged

bhavsarpratik merged 6 commits into
mainfrom
feat/git-commit-type-accuracy

Conversation

@bhavsarpratik

Copy link
Copy Markdown
Member

What

Follow-up to #139. It makes commit work-type labels more accurate for commits without a conventional prefix, and closes the gaps that PR listed.

  • More evidence for the model. For each commit, the local model now also sees:

    • the first body line that isn't a trailer;
    • a content-free line counting changed files by kind, plus total added and deleted lines, for example Changed files: 3 code, 1 tests (+120 / -30 lines).

    File paths and names never leave the Git scan. The question version moves from c1 to c2, so existing labels are redone once.

  • Conventional prefixes are taken as written. A lowercase Conventional Commits type is the label, with no model call and no queue slot. The types are feat, fix, docs, style, refactor, perf, test, build, ci and chore. The model still handles capitalised prefixes (Fix:), package-style prefixes (testing: or config: in Go or Linux history), and revert:.

  • MCP git_stats. It now returns the estimated split by work type, in the summary and in each day row. Keys are a fixed set and values are counts only.

  • Edge cases handled:

    • Common Git and Gerrit trailers are skipped: any …-by:, Change-Id, Cc, and Fixes: or Refs: followed by an id.
    • Renames count under their new name, and quoted non-ASCII paths are decoded.
    • When a commit is too big to list its files in time, it is labeled from its message alone, and the model is told "Changed files: not listed." rather than "none".
  • Tests. A failed rev-list in the backfill walk now has a test. The commit-type tests move to tests/test_git_commit_types.py to keep tests/test_git_delivery.py within its size budget.

Accuracy

The reference is each commit's own conventional-commit type, with the prefix stripped from the subject. It's a proxy for free-form messages, and the only labeled reference available.

The data is 664 of my own non-merge commits from the last 180 days, across 3 repos, labeled by token-meter-gemma. Commits are split by hash. Variants were chosen on 120 dev commits and scored once on 200 held-out commits, using both option orders.

Model input Held-out agreement With a body (167) No body (33)
Subject only (c1, shipped in #139) 57.5% 61% 39%
Subject + first body line + file kinds (c2) 72.0% 73% 67%

On the dev split:

Tried Result Shipped
File names on top 65% with or without them No
Prior correction Under 1 point gain No
Body line only 64.2% Superseded by body + kinds
File kinds only 65.0% Superseded by body + kinds

The no-body row is small (about ±16 points), so it shows direction only. The scratch data and the harness read private Git history, so both were deleted; only these numbers were kept. specs/WORK_INSIGHTS.md documents the method and its caveats.

Privacy: needs owner sign-off

This widens what Work insights may read. The rule in specs/AGENTS.md (and root AGENTS.md) now also allows:

  • the first body line (200 characters);
  • content-free counts of changed file kinds and lines.

Both sit alongside the commit subject. They stay in the in-memory queue and are sent only to the loopback model. Only labels persist. SECURITY, ARCHITECTURE, USER_GUIDE, README and the Git tooltip are updated to match. Tests check that no path, file name or body text reaches the classifier arguments or any stored file.

Validation

  • Full suite on 622683d: python3 scripts/run-tests exits 0, with 2587 tests passed and 21 skipped. scripts/check-page, py_compile, git diff --check, and the design-system, agent-workflow and size-budget contracts all pass.
  • Gates: one tester and two reviewers (privacy/persistence lens and correctness/evaluation lens) over five rounds, ending with no open findings. The tester mutation-checked every new guard. Each of these mutations is caught by a test:
    • remove a rename-resolution step;
    • narrow the trailer rule;
    • remove the fallback;
    • remove the prefix generation fence or the off/pause guard;
    • make prefixes case-insensitive.
  • Real-model run through the production path on 12 free-form commits: all 12 labeled, mean confidence 0.88.
  • Installed runtime: ./scripts/install reports installed commit 622683d. Both LaunchAgents are running, /health and /menubar return OK, and source and runtime match for every changed runtime file. Live /git-delivery shows c2 relabeling under way: 108 of 343 commits after the first scan.

Known limits (reviewed and accepted)

  • Accuracy figures come from one author on one machine with a proxy reference, so real free-form messages may score lower.
  • A few plain-English lines are dropped as trailers, such as Fixes: defaced banner or Fixes: 3 flaky tests. The model then gets the next body line.
  • File kinds can be wrong for renames with nested literal braces (dir/{a{1} => b}/x.py). This affects only the content-free count.
  • After updating, the chart shows earlier labels as not labeled yet until they are redone, at up to 200 commits per 5-minute scan. Prefixed commits are done without model calls.

🤖 Generated with Claude Code

bhavsarpratik and others added 6 commits November 6, 2026 12:42
Commits without a conventional prefix were labeled from the subject alone
(58% agreement with their authors' own type). The model now also sees the
first body line and a content-free count of the kinds of files the commit
changed ("Changed files: 3 code, 1 tests (+120 / -30 lines)."); paths and
names never leave the Git scan. Held-out agreement rose from 57.5% to 72.0%
(67% vs 39% for commits with no body). Question version c2 relabels once.

A conventional prefix (feat:, fix(ui):, docs:, chore:, ...) is now taken as
the label directly, with no model call or queue slot.

Also from #139: MCP git_stats returns the estimated work-type split
(summary and per day, enum keys, counts only), and a failed rev-list in
the backfill walk has a test.

Co-Authored-By: Claude Code <noreply@anthropic.com>
- Only lowercase Conventional Commits types are taken as the label;
  capitalised and package-style prefixes (Fix:, testing:, config:) go to
  the model.
- Common Git and Gerrit trailers (Reviewed-by, Change-Id, Cc, ...) are not
  taken as the detail line.
- File kinds use each file's new name, with quoted non-ASCII paths
  unquoted and renames resolved.
- A commit too large to list its files is labeled from its message alone.
- MCP method now says conventional_prefix_or_local_model; the chart note
  no longer claims every label came from the model.
- Docs: calibration method, scope (prefix-free commits only), sample
  limits, line totals in the files line, and the one-time relabel.
- Tests for the prefix path's off, pause, clear-fence, and storage-error
  handling; revert a stray test edit.

Co-Authored-By: Claude Code <noreply@anthropic.com>
… renames

When only a commit's message could be read, the model was told no files
changed; it now hears the files were not listed. Quoted renames are split
before unquoting so each side's kind is read correctly. Trailers come from
an explicit allowlist, so detail lines such as "Follow-up:" or "Fixes:"
are kept. Comments and docs state the lowercase-only prefix choice and the
calibration reference precisely.

Co-Authored-By: Claude Code <noreply@anthropic.com>
… note

- Renames resolve braces first, then split each quoted side, so a name
  containing "{" still classifies by its new path; the test now uses old
  and new names of different kinds so it fails without the split.
- Any "...-by:" key and Fixes/Closes/Resolves/Refs followed by an id are
  trailers; "Fixes: the crash" stays a detail line.
- The message-only fallback says "Changed files: not listed." without
  guessing a cause.

Co-Authored-By: Claude Code <noreply@anthropic.com>
tests/test_git_delivery.py had grown past its size budget, so the commit
work-type tests move to tests/test_git_commit_types.py unchanged (same 122
tests across the two modules). The rename fixture now has an old name that
looks like tests where the new name is code, for both the plain and the
brace form, so dropping either rename step fails the test.

Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
@bhavsarpratik

Copy link
Copy Markdown
Member Author

Owner sign-off on the privacy rule change: Pratik approved widening the Work insights rule so the local model may also read each pushed commit's first body line and content-free counts of the kinds of files and lines it changed. As before, these are sent only to the loopback model and never stored; paths and file names never leave the Git scan.

— Posted by Pratik's agent

@bhavsarpratik
bhavsarpratik merged commit e905cef into main Nov 6, 2026
10 checks passed
@bhavsarpratik
bhavsarpratik deleted the feat/git-commit-type-accuracy branch November 6, 2026 08:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant