# Mantis → Bug Fix → MR pipeline

End-to-end: a Mantis bug surfaces (via a Forge **Job** polling mantis.get_bug
/ mantis.search_bugs) → triggers a Pipeline → Pipeline checks out the right
base branch in a worktree → headless Claude implements the fix → pipeline
opens a GitLab MR via `glab` → pipeline pings the assignee + reporter on
Teams with the MR URL.

Mirrors the `gitlab-issue-fix-and-review` builtin but driven by Mantis
content (description / priority / category / assignee / reporter), MR
opens against an explicit `base_branch` you pass in from the Job (because
Mantis doesn't carry milestones the way GitLab issues do).

## Builtin name

`mantis-bug-fix-and-mr` — registered in Forge's built-in workflow set,
visible in the Pipelines view's workflow dropdown and pickable from the
extension Jobs tab when you wire a Pipeline-dispatch Job.

## Inputs (set by the Job's `input_template`)

| Key | Required | Source |
|---|---|---|
| `bug_id` | yes | `{{item.id}}` |
| `project` | yes | injected by `triggerPipeline` from the Job's project setting |
| `base_branch` | **yes** | `{{item.product_version}}` mapped to a branch, OR a literal like `"release/25.4"` you write into the template |
| `summary` | yes | `{{item.summary}}` |
| `description` | yes | `{{item.description}}` |
| `priority` | opt | `{{item.priority}}` |
| `category` | opt | `{{item.category}}` |
| `assignee` | opt | `{{item.assignee}}` — used as Teams chat name |
| `reporter` | opt | `{{item.reporter}}` — used as Teams chat name |
| `extra_context` | opt | literal hint text for Claude |
| `mr_title_template` | opt | default `Fix Mantis #{bug_id}: {summary}` |
| `mr_body_template` | opt | default closes-ref + Claude summary; vars `{bug_id} {summary} {description} {claude_summary}` |
| `teams_message_template` | opt | default `🤖 Mantis #{bug_id} fixed — please review MR: {mr_url}\nBug: {summary}`; vars `{bug_id} {summary} {role} {mr_url}` |

## Nodes

```
resolve              parse git remote → HOST + PROJECT_PATH; check glab; require base_branch
worktree-setup       git worktree add -b fix/mantis-<id> .forge/worktrees/mantis-<id> origin/<base>
fix-code             headless Claude — reads bug context, edits in worktree, commits
push-and-mr          if any commits: push fix/mantis-<id>, glab mr create → MR_URL
notify-teams         curl /api/connector-tool teams.send_message twice (assignee + reporter)
```

`fix-code` runs as a normal Claude task (not shell) so it can think, browse,
edit, and commit. `notify-teams` short-circuits if `push-and-mr` couldn't
create an MR (e.g. Claude made no changes).

## Talking to connectors from a pipeline

`notify-teams` calls `POST http://127.0.0.1:8403/api/connector-tool` —
a loopback-only endpoint that wraps `lib/chat/tool-dispatcher`. Body:

```json
{ "plugin_id": "teams", "tool": "send_message",
  "input": { "name": "Alice", "text": "MR: https://..." } }
```

Returns the standard `{ content, is_error }` tool-result shape. Works for
`browser` / `http` / `shell` protocol connectors. Browser-protocol calls
need the extension bridge connected at pipeline runtime (Chrome open +
extension signed in).

## Wiring it up from a Job

1. Confirm `glab` is installed + authed for the target host:
   `glab auth login --hostname <your-gitlab.example.com>`
2. Make sure the project has a git remote (`git remote get-url origin`) the
   `glab mr create` call can reach.
3. Forge extension → Jobs → + New job. Pick:
   - Connector / tool: `mantis` / `get_bug` (single bug) or `search_bugs` (a query)
   - dispatch: Pipeline → workflow `mantis-bug-fix-and-mr` → your project
   - `input_template` (auto-prefilled when you pick the workflow; map item
     keys to inputs, hardcode `base_branch` if Mantis doesn't carry it):
     ```json
     {
       "bug_id":      "{{item.id}}",
       "summary":     "{{item.summary}}",
       "description": "{{item.description}}",
       "priority":    "{{item.priority}}",
       "category":    "{{item.category}}",
       "assignee":    "{{item.assignee}}",
       "reporter":    "{{item.reporter}}",
       "base_branch": "release/25.4"
     }
     ```
4. Save → Run now (first tick is a backfill-no-op; click Reset dedup +
   Run now to force one actual dispatch for testing).

## Customising

- Branch derivation rules (e.g. `target_version` → `release/<major.minor>`):
  edit the `resolve` node in your local copy at
  `~/.forge/data/flows/mantis-bug-fix-and-mr.yaml` (a copy will be created
  when you click Edit in the Pipelines view; once a local file exists it
  overrides the builtin).
- MR title / body templates: set via the Job's `input_template` — no
  pipeline edit needed.
- Teams routing: today it uses the Mantis username verbatim as the Teams
  chat name (substring match). For better mapping, post-process in
  `notify-teams` to convert username → real name via a lookup table or
  a second connector call.

## Troubleshooting

| Symptom | Cause + fix |
|---|---|
| `ERROR: base_branch is required` | The Job's `input_template` didn't supply it — Mantis bugs don't always carry one. Hardcode it in the template or map from `product_version`. |
| `NO_CHANGES — Claude did not commit` | Bug description was too thin or Claude couldn't find the affected code. Add hints via `extra_context` or open the worktree manually and iterate. |
| `glab mr create` returns nothing | Token expired / target branch protected / source branch already has an open MR. The pipeline falls back to `glab mr view` to surface the existing URL — check Pipelines log. |
| Teams send returns `No extension connected to the bridge` | The pipeline ran when Chrome / extension weren't online. Notification fails but the MR still landed; re-fire the `notify-teams` node manually or message the people yourself. |
| Mantis username doesn't match any Teams chat | The fuzzy substring match in `teams.send_message` falls back to whatever the LLM-less DOM script can match. Add a lookup step before `notify-teams` to translate names. |
