Signal types
Every GitHub event is normalized into one of these signal types. The list is defined in types/scoring/signals.ts as the SIGNAL_TYPES const array.
| Signal | Type | Default Base Points | Description |
|---|---|---|---|
| Commit | commit | +10 | Git commit to any branch |
| PR merged | pr_merge | +50 | Pull request merged |
| PR review | review | +20 | PR review submitted |
| Issue opened | issue_open | +10 | New issue opened |
| Issue closed | issue_close | +10 | Issue closed |
| Comment | comment | 0 | Issue/PR comment (tracking-only by default) |
| PR opened | pr_open | — | Tracking only, no base points |
| PR closed no merge | pr_close_no_merge | — | Penalty-only |
| Spam | spam | — | Penalty-only |
Scoring algorithm — per signal
Each signal passes through these stages in order. The processing happens in lib/scoring/engine.ts.
1. Zero-point conditions check
2. Penalty check (spam, PR closed no merge)
3. Bot-activity check (zero points if detected)
4. Daily quotas & diminishing returns (contributor only)
5. Multiplier application1. Zero-point conditions
If any condition matches, the signal earns 0 with no penalty. These are configurable in the ruleset.
| Condition | When it fires |
|---|---|
self_review | A user reviews their own PR |
self_merge | A user merges their own PR |
bot_activity | Author is detected as a bot (via author.type or login suffix) |
issue_closed_no_pr | Issue closed without a linked PR (body parsed for closing keywords) |
pr_closed_no_merge | PR closed without merging (also triggers penalty below) |
2. Penalties
Applied directly to the total score via a penalty accumulator:
| Signal | Default Penalty | Notes |
|---|---|---|
| Spam | −12 | Per spam signal |
| PR closed no merge | −10 | Per PR closed without merging |
3. Multipliers
Multipliers stack multiplicatively. Order is fixed:
| Multiplier | Factor | Applies To | Condition |
|---|---|---|---|
first_activity | ×1.5 | First time user performs this type | Fires once per type per compute |
merged_pr_commit | ×1.2 | commit | SHA matches a PR’s merge commit SHA |
pr_linked_to_issue | ×1.1 | pr_merge, commit | PR body contains Closes #N, Fixes #N, or Resolves #N |
4. Daily quotas (contributor only)
Limits the number of signals per type that earn points per day. Beyond-quota signals earn zero points.
| Type | Default Limit |
|---|---|
| Commit | 4/day |
| Comment | 4/day |
5. Diminishing returns (contributor only)
After exceeding the weekly threshold, each additional signal of the same type loses value:
weeklyThreshold: 9
decayFactor: 0.11 (11% per additional signal)
floorFraction: 0.2 (minimum 20% of base value)Example for commits at 10 base points:
| Weekly Count | Effective Base |
|---|---|
| 1–9 | 10.0 |
| 10 | 8.9 |
| 11 | 7.8 |
| 12 | 6.7 |
| … | … |
| 20+ | 2.0 (floor) |
Signal metadata
Each signal carries a metadata object with per-type context. This is stored as JSONB in the signals table and used by scoring rules for contextual decisions.
| Signal Type | Metadata Fields |
|---|---|
| Commit | sha, isInMergedPR, message, isBot |
| PR merged/open | prNumber, title, body, linkedIssueIds, isSelfMerge, hasLinkedIssue, isBot |
| PR closed | prNumber, title, isBot |
| Issue open | issueNumber, title, body, isBot |
| Issue close | issueNumber, title, linkedPRNumber, hasLinkedPR, isBot |
| Review | prNumber, state, isSelfReview, body, isBot |
| Comment | commentId, issueNumber, body, isBot |
The isBot flag is propagated from the GitHub API response (type: Bot or login ending in [bot]) through the fetch and normalize layers.
Related
- Overview — architecture, pipeline, entity types
- Configuration & Presets — default ruleset, custom presets, API
- Reference — DB schema, types, module map