# Label Resolver

The label resolver (`src/pipeline/label-resolver.ts`) determines which GitHub labels to apply when
the bot creates a PR. It merges two label sources (flow and tribe), validates them against the
repo's actual labels, and returns the result.

## Label Sources

Every PR gets labels from up to two sources, all computed on every run:

| Source | Origin | Validated against repo | Fallback when GitHub API fails |
|--------|--------|----------------------|-------------------------------|
| **Flow labels** | `repos.json` → `prLabels` config, keyed by Jira flow field | Yes — non-existent labels are discarded with warning | Added without validation |
| **Tribe label** | `SQUAD_TRIBE_MAP` + `TRIBE_LABEL_CANDIDATES` in `constants.ts` | Yes — first matching candidate wins | **Not added** (candidates are generic, too risky) |

> **Note:** Agent-suggested labels (from Cursor agent output) were removed. The pipeline determines all
> labels deterministically from the Jira flow field and issue key. This avoids the agent incorrectly
> guessing flow labels based on issue type (e.g. adding `"stg fix"` for a Bug ticket with regular flow).

## Flow

```
resolveLabels(githubClient, repo, flow, issueKey, logger)
  │
  ├─ Fetch repo labels from GitHub (listRepoLabels)
  │   └─ Returns [] on any API error (401, 403, 404, network)
  │
  ├─ Build lowerToActual map (lowercase → canonical name)
  │   └─ null if repo labels fetch failed
  │
  ├─ Resolve flow labels
  │   ├─ Read from repo.prLabels config (bugfix/hotfix/default keyed by flow)
  │   ├─ If lowerToActual exists → validate each, warn on mismatches
  │   └─ If null → add all without validation
  │
  ├─ Resolve tribe label
  │   ├─ Extract project prefix from issueKey (e.g. SQDP → "SQDP")
  │   ├─ Look up tribe name in SQUAD_TRIBE_MAP
  │   ├─ Look up candidate labels in TRIBE_LABEL_CANDIDATES
  │   ├─ If lowerToActual exists → find first candidate that exists in repo
  │   └─ If null → skip entirely (no tribe label)
  │
  └─ Return final label array
```

## Flow Labels Config (`repos.json`)

Each repo can declare labels per flow type in `prLabels`:

```json
{
  "prLabels": {
    "bugfix": ["stg fix"],
    "hotfix": ["hot fix"],
    "default": []
  }
}
```

The flow field comes from Jira custom field `customfield_10200` on the issue or its parent. When flow is null, no flow labels are added.

| Flow value | Config key used | Fallback |
|-----------|----------------|----------|
| `"hotfix"` | `prLabels.hotfix` | `prLabels.bugfix` |
| `"bugfix"` | `prLabels.bugfix` | — |
| `"regular"` | `prLabels.default` | — |
| `null` | — | No flow labels |

### Current repo config

| Repo | bugfix | hotfix | default |
|------|--------|--------|---------|
| `humand-main-api` | `["bugfix"]` | `["bugfix", "hotfix"]` | — |
| `humand-mobile` | `["🧪 Bugfix"]` | `["🚨 Hotfix"]` | — |
| `humand-web` | `["stg fix"]` | `["hot fix"]` | — |
| `humand-backoffice` | `["stg fix"]` | `["hot fix"]` | — |
| (other repos) | not configured | not configured | — |

Repos without `prLabels` get no flow labels. This is intentional — we don't know what labels other
repos use, and it's better to omit than to add incorrect labels.

## Case Handling

All label comparison is case-insensitive. The canonical label name (as it exists in the GitHub repo)
is always used in the result, regardless of how the config spelled it.

Example: config has `"Stg Fix"`, repo has `"stg fix"` → result contains `"stg fix"`.

## Design Decisions

1. **No agent labels.** The agent used to suggest labels via `PR_LABELS_START`/`PR_LABELS_END`, but
   in practice all useful labels (flow + tribe) are determined deterministically by the pipeline.
   The agent often guessed wrong (e.g. adding `"stg fix"` for a Bug ticket on regular flow). Since
   flow labels are stripped from agent output anyway, and tribe labels come from config, agent label
   suggestions added no value and were removed entirely.

2. **Flow labels from config are authoritative.** The pipeline reads the Jira flow field and maps it
   to labels via `repos.json`. This is the single source of truth for flow-related labels.

3. **Tribe labels require validation.** Tribe label candidates are generic (e.g. `["comm",
   "communication", "Communication"]`) and may not exist in every repo. They are only added when the
   GitHub API confirms they exist. When the API is unavailable, tribe labels are skipped to avoid
   adding non-existent labels.

4. **Flow labels don't require validation when API fails.** When the GitHub API fails, flow labels
   (configured by us in `repos.json`) are added without validation. It's better to attempt adding
   them and let GitHub reject if needed, than to silently omit all labels.

## Log Events

| Event | Level | When | Fields |
|-------|-------|------|--------|
| `flow_labels_resolved` | info | Flow labels computed from config | `{ flow, flowLabels }` |
| `flow_label_not_in_repo` | warn | A flow label from config doesn't exist in repo | `{ flow, label }` |
| `tribe_label_resolved` | info | Tribe label found in repo | `{ tribe, tribeLabel }` |
| `tribe_label_not_found_in_repo` | info | No tribe candidate exists in repo | `{ tribe, candidates }` |

## Happy Paths

| # | Scenario | Flow | Config | Result |
|---|----------|------|--------|--------|
| 1 | Web bugfix | `bugfix` | `bugfix: ["stg fix"]` | `["stg fix", "<tribe>"]` |
| 2 | Web hotfix | `hotfix` | `hotfix: ["hot fix"]` | `["hot fix", "<tribe>"]` |
| 3 | Web regular | `regular` | no default | `["<tribe>"]` |
| 4 | API bugfix | `bugfix` | `bugfix: ["bugfix"]` | `["bugfix", "<tribe>"]` |
| 5 | Mobile hotfix | `hotfix` | `hotfix: ["🚨 Hotfix"]` | `["🚨 Hotfix", "<tribe>"]` |

## Edge Cases

| # | Scenario | Flow | Config | Result | Note |
|---|----------|------|--------|--------|------|
| 6 | Flow is null | `null` | `bugfix: ["stg fix"]` | `["<tribe>"]` | No flow label without flow value |
| 7 | No prLabels config | `bugfix` | none | `["<tribe>"]` | Only tribe |
| 8 | Tribe has no match in repo | `bugfix` | `bugfix: ["stg fix"]` | `["stg fix"]` | Tribe skipped |
| 9 | Squad without tribe (SQDO) | `bugfix` | `bugfix: ["bugfix"]` | `["bugfix"]` | No tribe for DevOps/Cross |
| 10 | `listRepoLabels()` fails | `bugfix` | `bugfix: ["stg fix"]` | `["stg fix"]` | No validation, no tribe |
| 11 | Flow label not in repo | `bugfix` | `bugfix: ["stg fix"]` | `["<tribe>"]` | Flow label discarded + warning |

All scenarios above have corresponding unit tests in `src/pipeline/label-resolver.test.ts`.
