Writing ticket and commit classification rules
The full reference for EngLedger's classification rule engines: every condition field, every operator, matching semantics, and what happens when rules change.
Updated 12 July 2026
This is the reference for the two classification rule engines: exact fields, operators and matching behaviour. For what classification is and where the results land, start with classification concepts; for worked examples of common setups, see the rules cookbook.
How rules evaluate
Both engines share the same evaluation model:
- Rules run in priority order (1 first) and the first matching rule wins. Nothing after it is considered.
- All conditions on a rule must match (AND). For OR logic, create several rules, or use a list operator ("is one of") on a single field.
- Only active rules evaluate. Deactivating a rule is a way to park it without deleting it.
- A new rule is appended at the lowest priority; drag rules to reorder.
- If no rule matches, the organisation's fallback classification
applies, and the result is recorded with source
fallback, visibly, not silently (see reading rule coverage).
For tickets there is one gate before any rule runs: ticket types on the organisation's excluded list are marked excluded and never reach the rules. Excluded work stays out of the ledger entirely.
Every rule tracks how often it has matched and when it last matched, so you can spot rules that never fire.
Ticket rules
Ticket rules (Classification → Ticket Rules) test fields on the synced ticket. A rule's output is a financial classification (capitalisable or non-capitalisable), a work type (feature, maintenance, support or technical debt), and optionally one or more categories.
Condition fields
| Field | Type | Notes |
|---|---|---|
| Issue key | Text | e.g. PROJ-123 |
| Issue type | Text | Story, Bug, Task, … |
| Status | Text | The ticket's current status |
| Summary | Text | The ticket title |
| Priority | Text | Jira and Bitbucket tickets |
| Story points | Number | Jira tickets |
| Labels | List | Jira and GitHub tickets |
| Components | List | Jira and Bitbucket tickets (Bitbucket: component) |
| Assignee | Text | Display name |
| Reporter | Text | Display name |
| Sprint | Text | Bitbucket: milestone |
| State reason | Text | GitHub issues (completed, not planned, …) |
| Provider | Text | jira, github or bitbucket, useful to scope a rule to one tracker |
Jira custom fields are also available as condition fields. EngLedger discovers them from your synced Jira data and lists them alongside the core fields, with the number of tickets that carry a value. Object-valued fields (selects, users) are matched on their display name or option value; multi- value fields behave as lists.
Operators by field type
| Field type | Operators |
|---|---|
| Text | equals, not equals, contains, not contains, starts with, ends with, is one of, is not one of |
| Number | equals, not equals, greater than, less than, greater than or equal, less than or equal |
| List | includes, not includes, includes one of, includes none of |
| Date | before, after, between |
Every field type also supports is empty and is not empty. A field counts as empty when it has no value, is an empty list, or contains only whitespace.
Matching semantics worth knowing:
- Text and list comparisons are case-insensitive.
bugmatchesBug. - "Is one of" / "includes one of" take a comma-separated list of values; spaces around commas are ignored. "Is one of" matches when the field equals any listed value; "includes one of" matches when a list field contains at least one of them.
- A missing value never matches a comparison operator: a ticket with
no story points fails both
story points greater than 3andstory points less than 3. Test for presence with is empty / is not empty. - Between is inclusive of both end dates. Date operators apply to Jira custom date fields.
Testing a rule before saving
The rule builder's test shows how many existing tickets the conditions match, with sample issue keys. The count respects priority: tickets a higher-priority rule would capture first are not counted, so the number reflects what the rule would actually classify from where it sits in the list.
Commit rules
Commit rules (Classification → Commit Rules) classify commits that don't
inherit a classification from a ticket. A commit linked to a ticket takes
that ticket's classification (source ticket); if it's linked to several,
a capitalisable one wins the tie. Commit rules only evaluate when there is
no usable ticket link, which is exactly their job: catching the unlinked
work described in the effort model.
Two other states short-circuit everything: a manual commit classification is never overwritten by rules, and a dismissed commit carries no classification at all.
Condition fields
| Field | Matches against |
|---|---|
| Branch name | The branch the commit was observed on |
| Commit message | The full commit message |
| File path | Each changed file's path: the condition matches if any changed file matches |
Operators
All commit rule fields are text and share one operator set:
| Operator | Behaviour |
|---|---|
| equals | Exact match, case-insensitive |
| contains | Substring match, case-insensitive |
| starts with / ends with | Prefix / suffix match, case-insensitive |
| matches (regex) | Regular expression match (see below) |
| in / not in | Comma-separated list of exact values, case-insensitive |
| is empty / is not empty | Presence test |
Regex matching takes a full pattern including delimiters, for example
/^(hotfix|release)\// against branch name, or /\.(md|mdx)$/ against
file path. Unlike the other operators, regex is case-sensitive unless you
add the i flag (/docs/i). A pattern that fails to compile simply never
matches. It doesn't error, so test it.
The commit rules screen includes a preview: paste a branch name, commit message or file list and see which rule would match and what classification would result, before any real commit is touched.
When rules change
Classification is retrospective. The engines keep already-classified work in line with the current rules, with two deliberate exceptions: manual overrides are never touched, and work in a locked reporting period is never touched (see period close).
For ticket rules:
- Editing or deleting a rule automatically re-evaluates the tickets that rule had classified. Each ticket runs through the full rule list again and takes whatever now matches: another rule or the fallback.
- Creating a rule doesn't reclassify anything by itself. New rules apply to newly synced tickets; to apply one to existing work, run Reclassify all tickets from the rules page. That re-evaluates every ticket except manual overrides, excluded types and locked periods, and reports how many changed and how many were skipped.
For commit rules, any change (create, edit or delete) automatically re-runs classification for commits currently classified by rule or fallback. Ticket-inherited and manual classifications are left alone.
Every ticket classification change, whatever triggered it, is written to an immutable history (previous values, new values, source and who triggered it) and the change flows through to the affected effort entries and any linked commits. The numbers move, and the record of the move is kept.
Override precedence
When several mechanisms could classify the same work, the order is fixed:
| Priority | Tickets | Commits |
|---|---|---|
| 1 | Manual override | Manual override |
| 2 | — | Linked ticket's classification |
| 3 | First matching rule | First matching commit rule |
| 4 | Organisation fallback | Organisation fallback |
A manual override (set from the review screens, or in bulk) wins over every rule and survives every reclassification run. Period locks sit above the whole table: once a period is locked, nothing in it reclassifies.
Reading rule coverage in the Confidence Report
Every effort entry records how it was classified, and the Confidence Report scores it: manual overrides highest, rule matches high, fallbacks low. The low-confidence grey zone is, in practice, a list of the work your rules don't cover: each fallback-classified entry is a ticket or commit no rule matched.
That makes the grey zone the working to-do list for rule authors: review what's in it, write or extend rules for the patterns you see, reclassify, and watch the zone shrink. The report shows honest coverage rather than hiding thin spots, the same posture as the rest of the ledger.
Related pages
- Classification concepts (the model: dimensions, sources, review and audit history).
- Rules cookbook (copy-ready recipes for common setups across all of EngLedger's rule engines).
- Scenario: R&D capitalisation (where rule quality ends up mattering: the period close).