Conversation
There was a problem hiding this comment.
Pull request overview
Adds configurable parsing of ticket IDs from GitLab MR descriptions and persists the parsed IDs onto imported GitLab work items, while keeping GitHub behavior unchanged.
Changes:
- Introduce
gitlab_ref_ticket_label(GITLAB_REF_TICKET_LABEL) config to enable/disable GitLab MR ticket parsing. - Parse and normalize ticket IDs from a labeled field in GitLab MR descriptions and store them on
GitLabMR.TicketIDs, then import intoWorkItem.TicketIDs. - Add unit/integration tests for parsing and GitLab fetch; update
/reportexamples and docs to use generic “ticket_id” wording.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| internal/integrations/gitlab/gitlab.go | Adds MR description ingestion and ticket parsing/normalization into GitLabMR.TicketIDs. |
| internal/integrations/gitlab/ticket_parse_test.go | Unit tests for description-field ticket parsing behavior. |
| internal/integrations/gitlab/gitlab_fetch_test.go | Integration test asserting FetchMRs populates TicketIDs when configured. |
| internal/domain/models.go | Extends GitLabMR with TicketIDs. |
| internal/fetch/auto_fetch.go | Persists mr.TicketIDs into imported WorkItem.TicketIDs. |
| internal/config/config.go | Adds GitLabRefTicketLabel config + env override allowing empty value. |
| internal/config/config_test.go | Tests default/override/empty behavior for GitLabRefTicketLabel. |
| internal/integrations/slack/slack.go | Updates /report help/usage examples to [ticket_id]. |
| internal/nudge/nudge.go | Updates nudge message example to [ticket_id]. |
| README.md | Documents the new config/env var. |
| config.yaml | Adds gitlab_ref_ticket_label sample config and explanation. |
| docs/agentic-architecture.md | Updates example wording from “mantis” to “ticket”. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| gitlab_url: "https://gitlab.example.com" | ||
| gitlab_token: "glpat-your-gitlab-token" | ||
| gitlab_group_id: "my-team-group" | ||
| # GitLab MR description field label used to parse ticket IDs (e.g. "Jira:") |
There was a problem hiding this comment.
The comment suggests configuring gitlab_ref_ticket_label with a trailing colon (e.g. "Jira:"). The parser already matches the colon after the label, so including a colon in the config value will make it look for "Jira::" and prevent ticket parsing. Update the example to "Jira" (no colon) to match the implementation.
| # GitLab MR description field label used to parse ticket IDs (e.g. "Jira:") | |
| # GitLab MR description field label used to parse ticket IDs (e.g. "Jira") |
| gitlab_url: "https://gitlab.example.com" | ||
| gitlab_token: "glpat-..." | ||
| gitlab_group_id: "my-team" | ||
| gitlab_ref_ticket_label: "Jira" # optional: parse ticket IDs from "Jira:" field in GitLab MR description; empty disables parsing |
There was a problem hiding this comment.
This README example implies the configured value might include the colon ("Jira:" field) but doesn’t explicitly say the config value should exclude it. Since the parser appends ':' in its match, clarify here that GITLAB_REF_TICKET_LABEL should be the label text without the trailing colon (e.g. Jira, not Jira:).
| gitlab_ref_ticket_label: "Jira" # optional: parse ticket IDs from "Jira:" field in GitLab MR description; empty disables parsing | |
| gitlab_ref_ticket_label: "Jira" # optional: label text without trailing colon; parser matches "Jira:" field in GitLab MR description; empty disables parsing |
| ticket = strings.TrimLeft(ticket, "#") | ||
| fieldPrefix := strings.TrimSpace(fieldLabel) + "-" | ||
| if fieldPrefix != "-" && len(ticket) >= len(fieldPrefix) && strings.EqualFold(ticket[:len(fieldPrefix)], fieldPrefix) { | ||
| ticket = ticket[len(fieldPrefix):] | ||
| } | ||
| ticket = strings.TrimSpace(strings.Trim(ticket, "[]")) |
There was a problem hiding this comment.
normalizeTicketToken trims surrounding brackets after stripping a leading '#'. Inputs like "[#7001005]" will keep the '#' because it isn’t at the start until after trimming brackets, so the function returns "#7001005" instead of "7001005". Consider trimming brackets first (and then removing a leading '#'/prefix), and add a unit test to cover the "[#123]" form.
| ticket = strings.TrimLeft(ticket, "#") | |
| fieldPrefix := strings.TrimSpace(fieldLabel) + "-" | |
| if fieldPrefix != "-" && len(ticket) >= len(fieldPrefix) && strings.EqualFold(ticket[:len(fieldPrefix)], fieldPrefix) { | |
| ticket = ticket[len(fieldPrefix):] | |
| } | |
| ticket = strings.TrimSpace(strings.Trim(ticket, "[]")) | |
| // First, trim surrounding brackets so forms like "[#123]" are normalized correctly. | |
| ticket = strings.Trim(ticket, "[]") | |
| // Then, remove any leading '#' characters. | |
| ticket = strings.TrimLeft(ticket, "#") | |
| fieldPrefix := strings.TrimSpace(fieldLabel) + "-" | |
| if fieldPrefix != "-" && len(ticket) >= len(fieldPrefix) && strings.EqualFold(ticket[:len(fieldPrefix)], fieldPrefix) { | |
| ticket = ticket[len(fieldPrefix):] | |
| } | |
| ticket = strings.TrimSpace(ticket) |
| for j := i + 1; j < len(lines); j++ { | ||
| next := strings.TrimSpace(lines[j]) | ||
| if next == "" { | ||
| continue | ||
| } | ||
| if markdownHeadingRe.MatchString(next) { | ||
| break | ||
| } | ||
| raw = next | ||
| break |
There was a problem hiding this comment.
When the configured field label line has no inline value, the next non-empty line is taken verbatim as the raw ticket string. If the value is provided as a markdown list item (e.g. "- #123" or "* TRACKER-123"), normalizeTicketToken won’t strip the list marker and the stored ticket ID will include the leading "-"/"". Consider stripping leading list markers ("- ", " ") from the next-line value (or handling them in normalizeTicketToken) and add a unit test for that format.
| fieldRe := regexp.MustCompile(`(?i)^\s*(?:#{1,6}\s*)?(?:[-*]\s*)?(?:\*\*|__)?\s*` + | ||
| regexp.QuoteMeta(fieldLabel) + `\s*(?:\*\*|__)?\s*:\s*(.*)$`) | ||
|
|
There was a problem hiding this comment.
parseTicketIDsFromDescription compiles fieldRe with regexp.MustCompile on every call (i.e., once per MR). Since FetchMRs already computes ticketFieldLabel once, consider compiling the regexp once per fetch (or caching per label) to avoid repeated regexp compilation when importing large numbers of MRs.
What changed
Tests
Notes