Skip to content

feat(jira): add --jira-trailer flag to extract Jira issue key from git trailer - #1109

Open
vidhu-balad wants to merge 23 commits into
mainfrom
feat/jira-trailer-flag
Open

feat(jira): add --jira-trailer flag to extract Jira issue key from git trailer#1109
vidhu-balad wants to merge 23 commits into
mainfrom
feat/jira-trailer-flag

Conversation

@vidhu-balad

Copy link
Copy Markdown
Contributor

Summary

  • Adds a --jira-trailer <key> flag to kosli attest jira
  • When set, the command reads only lines of the form <key>: <value> from the commit message and uses those values as the sole source of Jira issue references
  • The full commit message body and branch name scan are skipped entirely, eliminating false positives from other trailers (e.g. Ona-Environment-Id: ONA-456) whose values happen to match the Jira key pattern
  • Key match is case-insensitive; multiple occurrences of the same trailer key are supported
  • All existing behaviour is unchanged for users who do not set the flag

Changes

  • internal/gitview/gitView.go — new GetTrailerValues(message, key string) []string function
  • internal/gitview/gitView_test.go — 6 unit tests covering no match, single match, case-insensitive key, multiple occurrences, non-matching trailers ignored, whitespace trimming
  • cmd/kosli/root.gojiraTrailerFlag constant
  • cmd/kosli/attestJira.go--jira-trailer flag wired into the issue-finding logic
  • cmd/kosli/attestJira_test.go — 3 integration tests: trailer used successfully, trailer absent (non-compliant but reported), trailer absent with --assert (error)

Test plan

  • make test_integration_single TARGET=AttestJiraCommandTestSuite — tests 27, 28, 29 cover the new flag
  • go test ./internal/gitview/... -run TestGitViewTestSuite/TestGetTrailerValues — unit tests for GetTrailerValues
  • make lint — passes with 0 issues

🤖 Generated with Claude Code

…iler

When --jira-trailer <key> is set, the command reads lines of the form
'<key>: <value>' from the commit message and uses those values as the
sole source of Jira issue references, skipping the full commit message
and branch name scan. This avoids false positives from other trailers
(e.g. Ona-Environment-Id) whose values happen to match the Jira key
pattern.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@claude

claude Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Claude finished @vidhu-balad's task in 6m 39s —— View job


Review — latest push (e7018a56)

  • Read the diff against origin/main
  • Static checks (go build ./..., go vet ./... — both clean)
  • Review internal/gitview trailer helpers
  • Review cmd/kosli/attestJira.go + help text
  • Check test coverage / goldens consistency
  • Post review

What's fixed since the last round

  • Test 29's stale golden is now correct — it carries the trailer 'Jira' was not found in the commit message warning, matching tests 28 and 36 (same trailer-less message). That was the one thing that would have failed the suite.
  • scanLines unifies the match rule. TrailerKeyExistsAnywhere no longer carries its own copy of the prefix logic, so all three exported helpers agree on what "matching" means by construction — which matters because run() calls TrailerKeyExists and TrailerKeyExistsAnywhere on the same message and would otherwise be able to print "not found in the commit message" for a trailer sitting in the last block.
  • The last-block rule closes Alex's revert-quoting case, and the new "a 'Jira' line was found outside the last block … and was ignored" warning closes the silent-failure it introduced (GitHub squash-merge bodies). Test 38 pins it; TestTrailerKeyExistsAnywhere and the "key present outside last block" case in TestTrailerKeyExists pin the distinguishing behaviour at unit level.
  • emptyFlagValueError is extracted and correctly placed — Alex's point 2. reportEmptyFlagValue's doc comment is intact and the new function has its own.
  • Colon/whitespace keys rejected at PreRunE with unicode.IsSpace, so --jira-trailer $'A\tB' is caught, not silently non-matching. Test 32 pins it.
  • Audit spec is consistent: jira-trailer is in both flags_to_test and flag_values, so require_full_coverage passes. jira-secondary-source is only in flag_values, never in the always-set flags dict, so the new mutual exclusion can't collide with the probe run.

Help text now matches the code end to end — flag help, Long, and the example all say "last block / final paragraph", and both flags document the mutual exclusion.

Remaining

  1. Project-filter warning can blame the wrong flaginline comment. --jira-project-key ABC + Jira: not-a-key reports "did not match project filter [ABC]" when the trailer value was never a Jira key. Non-blocking.

  2. run() scans the message three times (GetTrailerValues, TrailerKeyExists, TrailerKeyExistsAnywhere) to build one diagnostic. Not a correctness or perf concern at commit-message size, and scanLines keeps them in agreement — but if you ever want to collapse it, exporting a single scan that returns (values, foundInBlock, foundAnywhere) would let the caller branch on one result. Optional.

  3. --ignore-branch-match warning is keyed on the value, not cmd.Flags().Changed (attestJira.go:360), so it also fires for a value inherited from KOSLI_IGNORE_BRANCH_MATCH or ~/.kosli.yml that the user didn't type on this command. Defensible either way — the setting genuinely is on and genuinely has no effect. Nit only.

Note on verification

go build ./... and go vet ./... both pass. I could not run go test or make lint in this environment (the commands were refused by the sandbox), so the goldens above are reasoned from the code rather than executed — worth running make test_integration_single TARGET=AttestJiraCommandTestSuite and go test ./internal/gitview/... locally before merge.

Nice work

The diagnostics are the strongest part of this PR now: four distinct warnings that each point at a different, actionable mistake (key absent, key in the wrong block, key present with no value, value not a Jira key), every one of them pinned by a golden. Together with the unit table covering the Unicode İ offset case, trailing-colon keys and the last-block boundary, the behaviour is well specified. The Long description is also genuinely accurate about what the flag does and does not protect you from, which is rarer than it should be.
· branch feat/jira-trailer-flag

Comment thread cmd/kosli/attestJira_test.go
Comment thread internal/gitview/gitView.go Outdated
Comment thread cmd/kosli/attestJira.go Outdated
Comment thread internal/gitview/gitView.go Outdated
Comment thread cmd/kosli/root.go Outdated
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment thread cmd/kosli/attestJira.go
@mbevc1

mbevc1 commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

@vidhu-balad there is still some feedback from the bot

@mbevc1 mbevc1 added the enhancement New feature or request label Aug 20, 2026
@github-actions github-actions Bot added go Pull requests that update go code feat labels Aug 25, 2026
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Comment thread cmd/kosli/attestJira_test.go
…curate help

- Trim leading whitespace from trailer lines so editor-indented or
  git-log-formatted messages (4-space indent) match correctly
- Strip trailing colon from --jira-trailer value so "Jira:" and "Jira"
  both produce the same prefix
- Error when --jira-trailer and --jira-secondary-source are both set
  (they are mutually exclusive; secondary source is silently ignored in
  trailer mode)
- Warn when --ignore-branch-match is set alongside --jira-trailer (it
  has no effect in trailer mode)
- Warn when a trailer is found but contains no valid Jira issue keys
- Thread issueSource through both --assert error messages so trailer
  mode names the trailer rather than "commit message or branch name"
- Update Long description to document trailer mode, its interaction with
  --ignore-branch-match, and its use as the preferred CVE-collision fix
- Add --jira-trailer example to attestJiraExample

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment thread cmd/kosli/attestJira.go Outdated
Comment thread cmd/kosli/attestJira.go Outdated
Comment thread internal/gitview/gitView.go Outdated
Comment thread cmd/kosli/attestJira_test.go
Comment thread cmd/kosli/root.go Outdated
Comment thread cmd/kosli/root.go Outdated
…canned test

- TrimSpace the key in GetTrailerValues before TrimRight(key, ":")
- Add unit test: key with surrounding whitespace still matches
- Add integration test 30: --jira-trailer does not scan branch name

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment thread cmd/kosli/attestJira.go Outdated
Comment thread cmd/kosli/attestJira.go Outdated
Comment thread cmd/kosli/attestJira_test.go
Comment thread cmd/kosli/root.go Outdated
Comment thread internal/gitview/gitView.go Outdated
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
- Add test 32: bare Jira: line triggers TrailerKeyExists warning path
- Rename old 32/33 → 33/34
- Fix example comment: replace "bypasses...entirely" with scoped claim
  matching the long desc caveat (trailer value still pattern-matched)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment thread cmd/kosli/testdata/empty-flag-audit-coverage.json
Comment thread cmd/kosli/attestJira.go
…r to audit spec

- Add test 34: --ignore-branch-match warns it has no effect in trailer mode
- Rename old 34 → 35
- Add jira-trailer to flags_to_test and flag_values in empty-flag-audit spec.json

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment thread cmd/kosli/attestJira.go Outdated
Comment thread internal/gitview/gitView.go Outdated
AlexKantor87
AlexKantor87 previously approved these changes Aug 29, 2026

@AlexKantor87 AlexKantor87 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice piece of work, Vidhu. The default path is untouched and the error messages now say which source was searched, which is a good touch. I pulled the branch down and had a proper dig through it. Two things I'd like sorted before it goes in, plus three smaller ones I've confirmed by running them.

1. It isn't really reading a git trailer

Git only treats the last block of a commit message as trailers. GetTrailerValues reads every line, so any line anywhere in the message that starts with Jira: counts. The comment on the function says this, but the flag help doesn't, and the flag help is what people will read.

Where it bites is revert commits. If someone reverts a commit and the old message gets quoted in the body, you get the old ticket as well as the new one. I ran this:

Revert "fix: thing"

This reverts commit abc123.

Original message was:
  Jira: EX-999

Jira: EX-1

That gives back both EX-999 and EX-1. Git itself would only give you EX-1.

It's not as bad as it sounds, because a line has to start with Jira: to match. Prose like "we now require Jira: EX-42 in every commit" is correctly ignored, which I also checked. So it's really just the quoted-revert case. But this flag exists to stop wrong tickets being picked up, and the help text says it "confines scanning to the trailer value", which nobody would read as "and also anything quoted in the middle of the message". Either restrict it to the last block, or say the limitation in the flag help and the long description rather than only in the code comment.

2. The same error sentence is now written in two places

The sentence flag '--jira-trailer' was given an empty value gets produced in two different ways now.

root.go:504 builds that sentence for any flag, by slotting the flag's name into a template. That's the existing rule and it already covers every flag in the CLI.

Your new check in attestJira.go types the same sentence out again as a literal, with jira-trailer baked into it.

To be clear, your check is doing real work and I don't want it removed. The existing rule only catches a completely empty value, so --jira-trailer : and --jira-trailer " " genuinely do get past it and genuinely do need catching. The only problem is the duplicated sentence. If anyone reworded the message later, the generic one would change and yours wouldn't, and users would see two different sentences for the same problem. 2026-08-15-how-the-empty-value-rule-is-built.md calls this out and says the wording is worth settling in one place, partly because a lot of tests check for it.

The fix is small. Pull the sentence into one function, something like:

func emptyFlagValueError(name string) error {
	return fmt.Errorf("flag '--%s' was given an empty value", name)
}

then have reportEmptyFlagValue return emptyFlagValueError(invalid.GetFlag().Name) and your check return emptyFlagValueError("jira-trailer"). One new function, two lines changed.

Three smaller ones. I've run each of these, so they're confirmed rather than suspected:

  • A key with a colon in it splits in the wrong place. With --jira-trailer "A:B" against a line A:B: PROJ-1, GetTrailerValues returns "B: PROJ-1" instead of "PROJ-1". The ticket still comes out right in the end, because FindJiraIssueKeys finds PROJ-1 inside that string anyway, but the value shown in the debug log and the warning is wrong. Git trailer names can't contain colons or spaces, so rejecting those alongside the existing empty check would close it in a line or two.

  • A missing trailer says nothing at all. Your test 28 pins this: commit with no trailer, no --assert, and the expected output is just the success line. The golden check is an exact comparison of everything the command printed, so that really is silence. The attestation goes up non-compliant and nobody is told why. It matches the behaviour of the existing path so I won't insist, but a "trailer 'Jira' not found in commit message" warning would be kinder, and you've already got TrailerKeyExists sitting there to do it.

  • The "no valid Jira issue keys" warning fires when the key was valid. FindJiraIssueKeys("EX-1", ["ABC"]) returns nothing, so with --jira-project-key ABC and a commit carrying Jira: EX-1, you get trailer 'Jira' was found but contained no valid Jira issue keys: [EX-1]. EX-1 is a perfectly valid key, it just belongs to another project. The wording sends people looking for a formatting problem that isn't there.

Approving on the basis that 1 and 2 get picked up.

@AlexKantor87 AlexKantor87 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please see comment

@AlexKantor87
AlexKantor87 dismissed their stale review August 29, 2026 06:57

Dismissing: this was submitted as an approval by mistake. The review content asks for two changes before merge, so an approval was the wrong state. The findings still stand, see the review body and the changes-requested review below.

@mbevc1

mbevc1 commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Good stuff, also to add to Alex's comments we sohuld update PR description, which is now stale (it still advertises 3 integration tests (27–29) and 6 unit tests, and don't mention the mutual exclusion with --jira-secondary-source - a hard error that changes what a valid command line is.

This branch carries 16 commits, most of them "fix the last round of bot feedback". Worth squashing per the slice guidance.

- Restrict GetTrailerValues/TrailerKeyExists to the final paragraph of the
  commit message, matching git interpret-trailers semantics
- Extract trailerBlock helper and scanTrailer to share one matcher
- Extract emptyFlagValueError to avoid duplicating the error wording
- Reject --jira-trailer keys containing colons or spaces (not valid in git)
- Add unit test: key in commit body but not final paragraph is not matched
- Add integration test 32: internal colon in key is rejected

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment thread cmd/kosli/root.go Outdated
Comment thread cmd/kosli/root.go Outdated
Comment thread internal/gitview/gitView.go Outdated
Comment thread cmd/kosli/attestJira.go Outdated
Comment thread cmd/kosli/attestJira.go Outdated
Replace "matching git interpret-trailers semantics" and similar
phrasing with plain descriptions of what the code actually does:
only the last block of lines is scanned (everything after the final
blank line, or the whole message if there is no blank line).

Also fix a misplaced doc comment for reportEmptyFlagValue/
emptyFlagValueError in root.go, and add a unit test case for a
single-paragraph commit message with no blank lines.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment thread cmd/kosli/attestJira.go Outdated
Comment thread cmd/kosli/attestJira.go Outdated
Previously a single warning fired when TrailerKeyExists was true but no
issue IDs were found, which conflated three different situations and
produced misleading messages:

- Trailer absent entirely: now warns that the key was not found in the
  last paragraph of the commit message, helping users diagnose cases
  where their trailer line is in the message body rather than the footer.
- Key present but value empty: now says "had no value" instead of
  "contained no valid Jira issue keys: []".
- Value present but yielding no IDs: distinguishes between a project-key
  filter mismatch ("did not match project filter") and a format problem
  ("did not contain valid Jira issue keys"), so users are not told their
  key format is wrong when it is actually filtered out.

Also update golden strings for tests 28, 33, 34, 36 and add test 37
for the project-key-filter case.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment thread cmd/kosli/attestJira_test.go Outdated
Comment thread cmd/kosli/attestJira.go Outdated
"The commit message body is not scanned" contradicted the preceding
sentence which says the last block of the commit message is scanned.
Replace with "The rest of the commit message" to avoid the collision.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment thread cmd/kosli/attestJira_test.go Outdated
Comment thread cmd/kosli/attestJira.go
vidhu-balad and others added 2 commits September 1, 2026 17:52
Add TrailerKeyExistsAnywhere to gitview, which scans the whole commit
message rather than only the final paragraph. Use it to split the
"trailer not found" warning into two cases:

- Key exists somewhere in the message but not in the last block:
  warns "a '<key>' line was found outside the last block of the commit
  message and was ignored" — names the actual mistake for squash-merge
  bodies where Jira: appears in the middle.
- Key absent from the message entirely:
  warns "trailer '<key>' was not found in the commit message".

Also fix test 29 whose golden was missed in da06c97 (same commit
message as tests 28 and 36, so the warning fires there too), update
tests 28, 29 and 36 to the new wording, and add test 38 for the
outside-last-block case.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
"commit message body" contradicted the preceding phrase "final
paragraph of the commit message". Use "rest of the commit message"
to match the long description.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment thread internal/gitview/gitView.go
TrailerKeyExistsAnywhere had its own copy of the line-matching loop,
diverging from scanTrailer despite needing to agree on what "matching"
means. Extract scanLines from scanTrailer; both scanTrailer and
TrailerKeyExistsAnywhere now delegate to it, so a single change keeps
all three exported helpers in sync.

Also add the missing unit coverage: a "key present outside last block"
case to TestTrailerKeyExists (must return false) and a new
TestTrailerKeyExistsAnywhere table covering key in last block, key
outside last block, and key absent entirely.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment thread cmd/kosli/attestJira.go
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request feat go Pull requests that update go code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants