Skip to content

feat(classify): add regex_fields whole-field AND rule type - #155

Open
TimeToBuildBob wants to merge 3 commits into
ActivityWatch:masterfrom
TimeToBuildBob:feat/category-regex-fields
Open

TimeToBuildBob wants to merge 3 commits into
ActivityWatch:masterfrom
TimeToBuildBob:feat/category-regex-fields

Conversation

@TimeToBuildBob

Copy link
Copy Markdown
Contributor

Summary

Adds a new regex_fields rule variant for category/tag classification that lets users constrain multiple fields simultaneously (logical AND) with whole-field matching semantics.

This addresses the use-case from ActivityWatch/aw-webui#939: when two apps (e.g. mstsc.exe for RDP, winbox.exe for Winbox) share the same window title, the existing regex rule cannot distinguish them because it OR-tests fields. A regex_fields rule with both app and title patterns resolves this exactly.

New rule format

{
  "type": "regex_fields",
  "fields": {
    "app": "mstsc\\.exe",
    "title": "office\\.example\\.com"
  },
  "ignore_case": false
}

All named fields must be present in the event data, be strings, and fully satisfy their pattern (re.fullmatch), so it must match the entire field value.

Backward compatibility

  • Existing regex rules with OR semantics are completely unchanged.
  • The new variant is strictly opt-in (requires "type": "regex_fields").
  • Rollout hazard guard: old Python silently ignores the unknown type field and reads the stale regex member. This variant explicitly raises ValueError if regex or select_keys are present alongside type: regex_fields.
  • ignore_case applies re.IGNORECASE to all field patterns.

What changed

  • aw_transform/classify.py: Rule.__init__ now dispatches on type field; new _field_patterns attribute; Rule.match uses fullmatch for the regex_fields path.
  • 11 new tests in tests/test_transforms.py covering AND semantics, whole-field anchoring, ignore_case, missing/non-string fields, empty-fields error, invalid field names/patterns, rollout hazard guard, and embedded newlines.

Testing

48 tests pass (pytest tests/test_transforms.py).

Companion PR for aw-server-rust (Rust server): ActivityWatch/aw-server-rust#670

Closes ActivityWatch/aw-webui#939 (partial — webui editor wiring is a follow-up once capability is advertised)

Extend Rule to support a 'regex_fields' variant where every named field
must match its own pattern in its entirety (re.fullmatch, logical AND).
Existing 'regex' rules are fully backward-compatible.

Motivation: users need separate categories when two apps (e.g. mstsc.exe
vs winbox.exe) show identical titles. Title-only regexes cannot tell them
apart; only an app-AND-title rule can.

Contract:
- type: 'regex_fields', fields: {<field>: <pattern>, ...}
- Optional ignore_case: bool (default False)
- All named fields must exist as strings in event.data
- Each pattern is tested with re.fullmatch (whole-field anchoring)
- 'regex' and 'select_keys' members raise ValueError on this variant,
  guarding against the rollout hazard where old Python reads a stray
  'regex' member and produces false positives
- Empty fields dict raises ValueError

11 new tests added to tests/test_transforms.py; 37 existing tests pass.

Git-Session-Id: 3f81
\n in a regex pattern is a newline metacharacter (matches actual newline),
so the previous assertion was wrong. Rewrote the test to document the
actual engine behavior: partial patterns don't match full-field values,
\n metacharacter matches embedded newlines, bare dot does not.

Git-Session-Id: 3f81
@greptile-apps

greptile-apps Bot commented Sep 10, 2026

Copy link
Copy Markdown

RetriggerConfidence Score: 5/5

The PR appears safe to merge; no outstanding correctness, security, or repository-rule issues remain.

Findings

  1. P2 Unknown Types Silently Fall Back
  2. P2 Malformed Patterns Escape Error Handling

Summary

  • Validates rule types and rejects incompatible legacy members.
  • Compiles each field pattern with shared case-sensitivity settings.
  • Requires every configured event field to contain a matching string value.
  • Adds coverage for matching semantics, malformed configurations, compatibility, and integration with categorization.
  • Both previous findings are fully addressed: unknown types are rejected and malformed field patterns are translated to ValueError.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Load rule configuration] --> B{Rule type}
    B -->|regex or omitted| C[Compile legacy regex]
    C --> D[Search selected or all event fields]
    B -->|regex_fields| E[Validate non-empty fields map]
    E --> F[Compile each field pattern]
    F --> G[Require every named field]
    G --> H[Full-match every string value]
    B -->|unsupported| I[Raise ValueError]
Loading

Reviews (2) · Last reviewed commit: "fix(classify): reject invalid rule confi..."

Comment thread aw_transform/classify.py
self.priority = _parse_optional_priority(rules)
flags = (re.IGNORECASE if self.ignore_case else 0) | re.UNICODE

if self._rule_type == "regex_fields":

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Unknown Types Silently Fall Back

Unsupported type values enter the legacy regex branch instead of being rejected. A misspelled regex_fields rule can silently never match or, if it includes a stale regex member, use legacy OR/search semantics and misclassify events. Validate that type is either regex or regex_fields.

Comment thread aw_transform/classify.py Outdated
raise ValueError(
f"regex_fields: pattern for field '{field}' must be a non-empty string"
)
self._field_patterns[field] = re.compile(pattern, flags)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Malformed Patterns Escape Error Handling

Compiling an invalid field pattern raises re.error, but the category and tag query wrappers only translate ValueError into QueryFunctionException. An invalid user-supplied regex_fields pattern therefore escapes the normal configuration-error path as an unexpected exception. Wrap compilation in a ValueError or explicitly translate re.error.

Git-Session-Id: 5673c0d5-27b3-5309-a5c9-0229d1db2bd2
@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

@greptileai review

@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

CI-green and mergeable (Greptile 5/5) — waiting only on a maintainer click.

This PR is ready to merge, but the bot has pull-only access to this repo and can't self-merge — surfacing it here so it isn't lost. The monitoring loop will stop re-flagging it now that this note is posted.

@TimeToBuildBob

TimeToBuildBob commented Sep 16, 2026

Copy link
Copy Markdown
Contributor Author

🤖 AI code review

Adds a new regex_fields rule variant to Rule in aw_transform/classify.py, selected by a type field, that requires all named fields to exist, be strings, and fully match their patterns via re.fullmatch. The legacy regex variant remains the default and keeps its OR/search semantics. Adds 11 tests in tests/test_transforms.py covering AND semantics, whole-field anchoring, ignore_case, missing fields, invalid patterns, and the rollout guard that rejects regex/select_keys on the new variant.

Safe to merge — no P0/P1 findings

Confidence 5/5

No findings. The diff looks correct to me on this pass.

Files changed (2) — the diff as I read it
  • aw_transform/classify.py — Adds _rule_type and _field_patterns attributes, dispatches __init__ on type, and adds a regex_fields branch in match using fullmatch.
  • tests/test_transforms.py — Adds 11 tests for the regex_fields rule variant, including AND semantics, whole-field anchoring, ignore_case, missing fields, invalid patterns, and the rollout guard.

Reviewed a9beedf00ee0 · openrouter/deepseek/deepseek-v4-flash-0731 · llm engine · 46s · about this reviewer

Maintainer commands

@TimeToBuildBob review (own line) — fresh review · @TimeToBuildBob fix — a worker acts on the findings. Once per comment; 👀 = received.

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.

Different interpretation of regex in the Activity and Timeline sections

1 participant