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
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 conditions → actions.
- 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
- 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.
- 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.ts — triage_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.
PRD: triage-rules-engine
Executive Summary
Every new GitHub issue currently arrives unlabeled and unrouted. Vincent manually adds
agent:*,complexity:*, andstatus:queuedto 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
(issue, rules)so it can be unit-tested without DB or networkProblem 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
match conditions→actions.Non-Goals
User Stories
complexity:*label, applycomplexity:lowandagent:claude", so that incoming bug reports start running through the pipeline without me touching them.Acceptance:
Functional Requirements
(issue, rules)so it can be unit-tested without DB or network.Non-Functional Requirements
failure_reasonUI, not crash the renderer.Success Criteria
title contains "bug"→add complexity:low, agent:claude, and gets those labels within one poll cycle.Out of Scope
Dependencies
packages/db/src/schema.ts—triage_rulestable +github_issue_cache.rules_applied_atcolumn.packages/agents/src/github/issue-poller.ts— call rules engine on new cache rows.packages/shared/src/triage-rules.ts(new) — pure evaluator.Verification Plan
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.title contains "bug"→add agent:claude, complexity:low.Risks & Open Questions
rules_applied_atset — backfill asnow()on schema deploy so they're never re-evaluated.