Skip to content

Triage rules engine for incoming issues #86

@VincentShipsIt

Description

@VincentShipsIt

PRD: triage-rules-engine

Executive Summary

Every new GitHub issue currently arrives unlabeled and unrouted. Vincent manually adds agent:*, complexity:*, and status:queued to start the pipeline. A small rules engine — match an issue's labels/title against a list of project-scoped rules, apply a target label set when matched — eliminates this work for the 80% case without sacrificing the manual override path.

Implementation Checklist

  • The system must persist a per-project ordered list of triage rules in the DB
  • Each rule must encode: a condition set (any-of / all-of over label inclusion, label exclusion, title-substring), an action set (labels to add, labels to remove), and an enabled flag
  • The issue-poller must, for each newly cached issue, evaluate rules in order and apply the first matching rule's actions exactly once. A rules-applied flag on the issue cache row must prevent re-firing
  • Rule evaluation must be a pure function over (issue, rules) so it can be unit-tested without DB or network
  • Rules edited via project settings must take effect on next poll without restarting the app

Problem Statement

Manual triage is repetitive and error-prone. Vincent has caught himself running the same five label-add commands across issue after issue. Linear has a richer rules system; we don't need rich, we need a tiny matcher with sane defaults.

Goals

  • Each project can define an ordered list of triage rules: match conditionsactions.
  • New issues caught by the issue-poller pass through the rules engine on first ingestion and have matching action sets applied automatically.
  • A simple UI in project settings lists, edits, and reorders rules.

Non-Goals

  • Re-running rules on already-triaged issues (idempotent first-pass only).
  • Cross-project shared rules library.
  • Time-of-day or author-based conditions.
  • Auto-close / auto-merge actions.
  • LLM-driven classification — rules are exact-match strings on label and title-substring.

User Stories

  • As a maintainer, I want to declare "if title contains 'bug' and no complexity:* label, apply complexity:low and agent:claude", so that incoming bug reports start running through the pipeline without me touching them.
    Acceptance:
    • Defining a rule via project settings persists it.
    • The next new issue that matches the rule receives the listed labels within one poll cycle.
    • The rule does not re-fire on the same issue across subsequent polls.

Functional Requirements

  1. The system must persist a per-project ordered list of triage rules in the DB.
  2. Each rule must encode: a condition set (any-of / all-of over label inclusion, label exclusion, title-substring), an action set (labels to add, labels to remove), and an enabled flag.
  3. The issue-poller must, for each newly cached issue, evaluate rules in order and apply the first matching rule's actions exactly once. A rules-applied flag on the issue cache row must prevent re-firing.
  4. Rule evaluation must be a pure function over (issue, rules) so it can be unit-tested without DB or network.
  5. Rules edited via project settings must take effect on next poll without restarting the app.
  6. A rule action that adds labels must reuse the existing label-sync helper.

Non-Functional Requirements

  • Rule evaluation must complete in <1ms per issue.
  • Failed action application must surface via the existing failure_reason UI, not crash the renderer.
  • Rules persistence must support up to 50 rules per project without UI degradation.

Success Criteria

  • A new GitHub issue with title "Bug: foo crashes" matches a rule of title contains "bug"add complexity:low, agent:claude, and gets those labels within one poll cycle.
  • The rule does not re-apply on subsequent polls.
  • Disabling a rule prevents it from firing on new incoming issues.
  • The rules list editor allows create / edit / reorder / delete with persistent state.
  • Tests cover ordering precedence and the once-per-issue idempotence guarantee.

Out of Scope

  • Re-applying rules to historical issues.
  • Author / time-based conditions.
  • Cross-project shared rule library.
  • Auto-close / auto-merge actions.
  • LLM-driven label inference.
  • Linear-source equivalent (covered by linear-issue-source PRD).

Dependencies

  • packages/db/src/schema.tstriage_rules table + github_issue_cache.rules_applied_at column.
  • packages/agents/src/github/issue-poller.ts — call rules engine on new cache rows.
  • packages/shared/src/triage-rules.ts (new) — pure evaluator.
  • Project settings modal — new "Triage rules" section.
  • Existing label-sync helper.

Verification Plan

  • tests:
    • packages/shared/src/triage-rules.test.ts — evaluator fixtures: ordering, exclusion, substring match, disabled rules.
    • packages/db/src/queries/triage-rules.test.ts — CRUD + reorder.
    • packages/agents/src/github/issue-poller.test.ts — once-per-issue idempotence.
    • apps/desktop/src/renderer/components/project-settings-modal/TriageRulesTab.test.tsx — UI editor flows.
  • manual:
    • Define a rule for title contains "bug"add agent:claude, complexity:low.
    • File a GitHub issue titled "Bug: x"; confirm both labels appear within one poll cycle.
    • Disable the rule; file another similar issue; confirm no labels are applied.
    • Reorder two rules and confirm precedence change reflects in evaluation.

Risks & Open Questions

  • Rule conflicts (two rules adding contradictory labels) — first-match-wins keeps semantics simple but may surprise users; document in UI.
  • Idempotence flag scope: per-rule or per-issue? Per-issue is simpler; per-rule lets re-enabled rules re-fire. v1 picks per-issue; revisit if users complain.
  • Migration: existing issues won't have rules_applied_at set — backfill as now() on schema deploy so they're never re-evaluated.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No fields configured for Feature.

    Projects

    Status

    In Progress

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions