# Jira → PR Fix Pipeline

Pipeline that detects Jira tickets assigned to the bot, triages them, applies a fix using a Cursor agent, validates the build, and opens a PR on GitHub.

---

## Overview

```
Jira ("To Do" tickets assigned to the bot)
    │
    ▼
JiraPoller  ─── every 5s ───► searches tickets by JQL
    │                          enqueues all results
    ▼
JobQueue (concurrency 1, max 2 retries, timeout 35min)
    │
    ▼
FixPipeline
    ├── 1. Move ticket to "In Progress"
    ├── 2. Fetch issue (description, attachments, comments)
    ├── 3. Sync repos (clone/pull + branch fix/KEY-summary)
    ├── 4. Sync remote rules repos
    ├── 5. Save images to .hu-bot/
    │
    ├── PHASE 1: Triage (read-only agent, timeout 16min)
    │       ├── NEEDS_INFO → comment on Jira, return to "To Do", abort
    │       └── PROCEED    → continue
    │
    ├── PHASE 2: Fix (write-enabled agent, timeout 30min)
    │       └── no changes → comment on Jira, abort
    │
    └── For each repo with changes:
            ├── getDiffStats (safety checks)
            ├── [multi-repo] create Jira subtask + dedicated branch
            ├── commitAndPush
            ├── validateBuild (install + check scripts)
            │       └── if fails → retry with followup agent (15min)
            ├── createPr on GitHub
            └── addLabels based on flow (hotfix / bugfix / default)
```

---

## 1. Poller (`JiraPoller`)

**File:** `src/poller/jira-poller.ts`  
**Interval:** 5 seconds (`POLLER.INTERVAL_MS`)

### What it does each cycle

1. **Guard conditions** — skips if:
   - A poll is already in progress
   - The queue is busy
   - A `pr_comment` job is running (both job types respect each other)

2. **Resolves the bot's `accountId`** on first run (looks up by email via Jira API).

3. Runs the JQL:
   ```
   assignee = "<botAccountId>" AND status = "To Do" ORDER BY created DESC
   ```

4. Enqueues **all results** as `jira` jobs.

### Enqueued payload

```typescript
{
  jobKind: "jira",
  issueId: string,       // Jira internal ID
  issueKey: string,      // e.g. "SQDP-3480"
  summary: string,
  description: string,
}
```

---

## 2. Queue

**Concurrency:** 1.  
**Max retries:** 2 (`QUEUE.MAX_RETRIES`).  
**Job timeout:** 35 minutes (`QUEUE.JOB_TIMEOUT_MS`).

---

## 3. Pipeline (`FixPipeline`)

**File:** `src/pipeline/fix-pipeline.ts`

### 3.1 Move ticket to "In Progress"

Transitions the Jira issue to `"In Progress"` before starting. If it fails, execution continues (non-blocking warning).

### 3.2 Fetch the full issue

Fetches from Jira:
- Summary, description, status, flow (`regular` / `hotfix` / `bugfix`)
- Parent ticket (if subtask): key, summary, description, type
- Acceptance criteria, stacktrace
- Previous comments (for agent context)
- Attachments (images to download)

### 3.3 Repo sync (`syncAllRepos`)

For **all** repos in the registry (`repos.json`):

```
checkoutBase(develop)  → cleans the working tree and pulls latest
createBranch("fix/KEY-summary-slug")
installDependencies()  → yarn/npm install (non-blocking)
```

The branch name is generated with `buildBranchName(issueKey, summary)`:  
`fix/SQDP-3480-login-button-not-working-on-mobile`

> If no repo can be synced, the pipeline aborts with `success: false`.

### 3.4 Remote rules sync (`syncRemoteRulesRepos`)

If any repo in `repos.json` has `remoteRulesRepo` set, that rules repo is cloned (or pulled) and its `.mdc` files are **symlinked** into `.cursor/rules/` of the code repo. This injects project-specific rules into the agent at runtime.

### 3.5 Save images to `.hu-bot/`

Downloads image attachments from the ticket (and its parent) to `workdir/.hu-bot/`.

- Max 5 images (`ATTACHMENTS.MAX_IMAGES`)
- Max 5MB per image (`ATTACHMENTS.MAX_SIZE_BYTES`)

The path is mentioned in the prompt so the agent can open them using its tools.  
Images are deleted at the end of the pipeline (`cleanupImages`).

### 3.6 Load repo contexts (`loadAllRepoContexts`)

For each repo, reads agent rules from:
1. `<repo>/.cursor/rules/hu-agent.mdc` (rules inside the repo; default index filename)
2. `repos.json[repo].rulesFile` (local bot-side path, fallback)

The `.mdc` file can be an index with relative links to other `.md` files. They are automatically concatenated up to a limit of 8000 chars.

---

## 4. Phase 1: Triage

**Timeout:** 16 minutes (`CURSOR_AGENT.TRIAGE_TIMEOUT_MS`)

The agent receives the triage prompt (`buildTriagePrompt`) and must respond with exactly one of these formats:

```
VERDICT: PROCEED
SUMMARY: <analysis>
```

```
VERDICT: NEEDS_INFO
SUMMARY: <analysis>
QUESTIONS:
- <question 1>
- <question 2>
```

### If NEEDS_INFO

1. Posts a comment on Jira with the analysis and questions.
2. Transitions the ticket back to `"To Do"`.
3. Returns `outcome: "needs_info"` — the ticket waits for more information.

### If PROCEED

Continues to the fix phase.

> **Fallback:** If the triage agent fails (exit code ≠ 0), `PROCEED` is assumed to avoid blocking valid tickets.

---

## 5. Phase 2: Fix

**Timeout:** 30 minutes (`CURSOR_AGENT.INITIAL_TIMEOUT_MS`)

The agent receives the fix prompt (`buildFixPrompt`), which includes:

- General bot rules (`rules/general.mdc`)
- The triage summary as prior context
- The ticket data (bug report, description, comments, images)
- Description and rules for **all** available repos

The agent works in `workdir/` with access to all cloned repos.

### If the agent produces no changes

Posts a comment on Jira with the agent output ("I investigated but could not produce a fix") and aborts.

### If the agent fails (exit code ≠ 0)

Posts a comment on Jira with the agent summary and aborts.

---

## 6. Post-fix: per repo with changes

### 6.1 Safety check (`getDiffStats`)

Validates diff limits before committing:

| Limit | Value |
|---|---|
| Max files changed | 10 |
| Max lines changed | 500 |
| Max files deleted | 3 |
| Forbidden paths | `.github/workflows/`, `.travis.yml`, etc. |

Lockfiles (`yarn.lock`, `package-lock.json`, etc.) are automatically excluded from the commit.

### 6.2 Subtasks for multi-repo (optional)

If the agent modified **more than one repo**, a Jira subtask is created per repo:

```
<subtaskPrefix> | <parent ticket summary>
```

Each subtask has its own branch: `fix/KEY-SUBTASK-slug`.  
If subtask creation fails, the original issue is used as fallback.

### 6.3 Commit and push

```
fix(<issueKey>): <ticket summary>
```

Push with `--force` to overwrite if the branch already existed.

### 6.4 Build validation (`validateBuild`)

1. **Install** — if `node_modules` doesn't exist or `package.json` changed.
2. **Check scripts** — runs scripts defined in `repos.json[repo].checkScripts` (e.g. `typecheck`, `lint`). If no scripts are defined, attempts `build`.

For each failing script:
- If exit code is 127 (binary not available), it is skipped.
- If errors are **not in files modified by the bot** (pre-existing errors), they are skipped.
- If errors **are in bot-modified files**, validation fails.

#### Followup agent retry

If the build fails, a second agent (`buildFollowupPrompt`) is run with the error output to fix the issues. Timeout: 15 minutes.

If the build still fails after the retry, that repo is skipped (`continue`) — no PR is created for it.

### 6.5 Create the PR on GitHub

```typescript
githubClient.createPr(branch, title, body, baseBranch)
```

**Title** — built from the repo's template (`prTitleTemplate` in `repos.json`):
```
[SQDP-3480] | Login button not working
```

**Body** — includes:
- Agent summary (first 2000 chars of output)
- Link to the Jira ticket
- Flow section (Regular / Hotfix / Bugfix with checkboxes)
- Build and automated test status

A 2-second delay is added before creating the PR to give GitHub time to see the branch after the push.

### 6.6 Labels

If the repo has `prLabels` configured in `repos.json`, labels are added based on the ticket flow:

| Flow | Labels |
|---|---|
| `hotfix` | `prLabels.hotfix` → `prLabels.bugfix` → `[]` |
| `bugfix` | `prLabels.bugfix` → `[]` |
| `regular` | `prLabels.default` → `[]` |

---

## 7. Result

The pipeline returns `success: true` with the list of created PRs. If no repo produced a PR (all failed build or safety checks), it returns `success: false`.

Repo branch cleanup (`stale_branches_cleaned`) happens in the PR comment pipeline, not here.

---

## 8. Reference logs

A full successful cycle looks like this:

```
poller_started                    → Jira poller started
bot_account_resolved_from_email     → bot accountId resolved from `JIRA_EMAIL`
job_received                      → ticket enqueued
pipeline_started                  → pipeline started
ticket_moved_to_in_progress       → Jira transition
all_repos_synced                  → repos ready with fix/... branch
rules_symlinked                   → remote rules linked (if applicable)
downloading_attachments           → ticket images
triage_completed  verdict=PROCEED → triage passed
cursor_cli_started (triage)       → triage agent running
cursor_cli_started (fix)          → fix agent running
cursor_cli_finished               → agent finished
changes_detected                  → repos with changes detected
diff_stats                        → filesChanged, linesChanged
committed_and_pushed              → fix committed
build_started / build_passed      → build validation
pr_created                        → PR opened on GitHub
pipeline_completed                → success
job_completed
```

---

## 9. Abort conditions

| Condition | Jira comment | Ticket status |
|---|---|---|
| No repos synced | No | Unchanged |
| Triage → NEEDS_INFO | Yes (analysis + questions) | Returns to "To Do" |
| Fix agent fails (exit ≠ 0) | Yes (agent summary) | Stays "In Progress" |
| Fix agent produces no changes | Yes ("could not produce a fix") | Stays "In Progress" |
| Build fails after retry | No (per repo) | Stays "In Progress" |
| Safety check fails | No (per repo) | Stays "In Progress" |
| All repos failed | No | Stays "In Progress" |
