# Automation state machine — labels, comments, statuses

This page documents what Forge's automation pipelines write to external
systems (GitLab, Mantis, Teams, Forge chat). Use it as the single reference
when wiring new schedules, triaging an MR, or auditing what Forge has
already done.

Three pipelines operate on a Mantis bug → GitLab MR pair:

| Pipeline                    | Triggered by              | Touches              |
|-----------------------------|---------------------------|----------------------|
| `fortinet-mantis-bug-fix`   | Manual fire / Schedule    | Mantis + GitLab      |
| `fortinet-mr-pre-review`    | Manual fire / Schedule    | GitLab               |
| `fortinet-mr-review`        | Manual fire / Schedule    | GitLab (+ Teams)     |

## Stage labels (mutually exclusive)

Every pipeline marks the GitLab MR with a `forge:stage/*` label when it
finishes. The labels are mutually exclusive — each pipeline removes the
others before adding its own. Reading the label tells you "how far through
the automation funnel" the MR is.

| Stage label                  | Set by                       | Meaning                                                 |
|------------------------------|------------------------------|---------------------------------------------------------|
| `forge:stage/needs-review`   | `fortinet-mantis-bug-fix`    | MR just created by Forge; nobody has reviewed it yet.   |
| `forge:stage/pre-reviewed`   | `fortinet-mr-pre-review`     | Forge posted an AI pre-review comment.                  |
| `forge:stage/reviewed`       | `fortinet-mr-review`         | Forge ran a full review round (replied / labelled / closed). |

**One-time setup per GitLab project:** create those three labels in the
project's Labels page. Pipelines call `glab api ... add_labels=...`; the
label has to exist or the API call silently fails (best-effort — no error
escalation, just `STAGE_LABEL=failed` in the run output).

## Verdict labels (orthogonal to stage labels)

`fortinet-mr-review` also adds a label that describes its **decision**.
These can co-exist with stage labels.

| Verdict label          | When                                                                |
|------------------------|---------------------------------------------------------------------|
| `forge:fix-applied`    | Triage said ACT, Claude pushed a fix commit, MR updated.            |
| `forge:no-fix-needed`  | Triage said SKIP (e.g. style-only review notes — no action needed). |
| `forge:no-merge`       | Triage said CLOSE — MR is invalid; pipeline also closes the MR.     |

## Per-pipeline detail

### 1. `fortinet-mantis-bug-fix`

Input: Mantis bug id (+ project). Walks the bug, opens a worktree on the
target branch, runs Claude to write a fix, pushes, opens MR, then
post-processes both sides.

| Step          | Side effect                                                                                                                                                                                  |
|---------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `push-and-mr` | Creates GitLab MR (or finds existing for the branch). **Sets `forge:stage/needs-review`** + removes other `forge:stage/*` labels.                                                            |
| `mark-fixed`  | Mantis **resolution → fixed**. Status (e.g. New / Assigned / Resolved) is left untouched — reviewer of the MR decides. Best-effort via Mantis connector + `connector-tool` bridge.           |
| `notify-mantis` | Adds a Mantis **bugnote** containing the MR URL, branch, and Claude's fix summary. Format starts with "Auto-fix applied — MR opened." and ends with `ref: autofix-<bug_id>`. Best-effort. |
| `notify-teams` | Posts a Teams chat message to the configured user/group with MR URL + summary. Best-effort.                                                                                                |
| `cleanup`     | Removes the temporary worktree.                                                                                                                                                              |

If `push-and-mr` finds no diff to commit (Claude bailed without changes),
`mark-fixed` skips (no resolution change), and `notify-mantis` posts a
**"Auto-fix attempted — no fix applied"** bugnote instead. So Mantis is
always notified one way or the other.

### 2. `fortinet-mr-pre-review`

Input: GitLab MR URL. Fetches metadata + diff + discussion, runs Claude
to draft a short analysis, posts ONE comment on the MR.

| Step           | Side effect                                                                                                                                                                                                         |
|----------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `ingest`       | Fetches MR meta / diff / notes. **Skip detection:** finds the last Forge pre-review note (body starts with `🤖 Forge pre-review:`); if there are no new commits AND no new non-forge notes since, sets `SKIP_PRE_REVIEW=yes`. Downstream steps short-circuit. |
| `analyze`      | Claude produces VERDICT (`ready-to-merge` / `needs-review`) + FINDINGS + REPLY DRAFT.                                                                                                                                |
| `post-comment` | Posts one GitLab note. First line is always `**Recommendation:** ✅ Looks safe to merge.` or `**Recommendation:** ⚠️ Needs another review — <reason>.`. **Sets `forge:stage/pre-reviewed`** + removes other `forge:stage/*`. On `SKIP_PRE_REVIEW=yes`, doesn't post or touch labels. |
| `notify-chat`  | Opens a Forge chat session seeded with the analysis (or "skipped" if skip path).                                                                                                                                    |

The skip path means re-running pre-review (manually or on a schedule)
won't spam the MR with duplicate comments. Adding any commit or non-forge
note to the MR re-arms the pipeline.

### 3. `fortinet-mr-review`

Input: GitLab MR URL + reviewer notes. Triages the discussion, optionally
fixes code + pushes, replies on the MR.

| Step       | Side effect                                                                                                                                                                                                                                                                                |
|------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `ingest`   | Fetches MR + reviewer notes since last Forge reply.                                                                                                                                                                                                                                          |
| `triage`   | Claude decides one of: `ACT` (push a fix), `SKIP` (no action), `CLOSE` (abandon the MR), `NOOP` (no new reviewer feedback, break self-reply loop).                                                                                                                                            |
| `fix`      | (ACT only) Claude edits + commits + pushes to the MR branch.                                                                                                                                                                                                                                 |
| `reply`    | Posts one GitLab note (`🤖 Forge: <RESULT block>`). Then: <ul><li>**Verdict label** by status: `pushed` → `forge:fix-applied`, `skipped` → `forge:no-fix-needed`, `close` → `forge:no-merge` + closes the MR.</li><li>**Stage label** (every status): `forge:stage/reviewed` + removes other `forge:stage/*`.</li></ul> |
| `cleanup`  | Removes the temporary worktree; posts to Teams.                                                                                                                                                                                                                                              |

`NOOP` doesn't touch labels or post anything — it exists purely to avoid
the pipeline replying to its own previous reply.

## Comments — who posts what

Every Forge-generated comment has a recognisable prefix so you can grep /
filter them in the MR or in Mantis:

| Source                                    | Where             | Prefix                              |
|-------------------------------------------|-------------------|-------------------------------------|
| `fortinet-mantis-bug-fix` → notify-mantis | Mantis bugnote    | `Auto-fix applied — MR opened.` / `Auto-fix attempted — no fix applied.` (ends with `ref: autofix-<bug_id>`) |
| `fortinet-mr-pre-review`  → post-comment  | GitLab MR note    | `🤖 Forge pre-review:`              |
| `fortinet-mr-review`      → reply         | GitLab MR note    | `🤖 Forge:` (followed by RESULT block) |

## Mantis side

| What                       | Set by                                                                                                                |
|----------------------------|-----------------------------------------------------------------------------------------------------------------------|
| **Resolution → fixed**     | `fortinet-mantis-bug-fix` → `mark-fixed`, only when an MR was actually created. Best-effort via Mantis connector.     |
| Bugnote with MR + summary  | `fortinet-mantis-bug-fix` → `notify-mantis`. Always posts (one variant if MR created, another "no fix applied" variant if Claude bailed). |

Mantis **status** is intentionally never auto-changed — the human reviewer
who merges the MR decides whether the bug moves to Resolved / Closed.

## Teams side

Both `fortinet-mantis-bug-fix` and `fortinet-mr-review` (via their final
`cleanup` / notify steps) send a Teams chat message with the MR URL and
brief summary, IF the Teams connector is configured. Best-effort — Teams
unreachable doesn't fail the pipeline.

## Forge chat seeds

`fortinet-mr-pre-review` → `notify-chat` opens a new Forge chat session
seeded with the analysis + four next-step options (Approve / Request
changes / Look closer / Nothing). The chat session id is returned so a
Schedule action can re-use the same session or open a fresh one each run.

## Quick reference — "where is this MR right now?"

```
Look at the MR's labels:

  forge:stage/needs-review     → bug-fix pipeline just opened it
  forge:stage/pre-reviewed     → AI pre-review posted; awaiting human
  forge:stage/reviewed         → Forge did a full review round
  (no forge:stage/* label)     → not Forge-managed, or pre-stage-label era

  forge:fix-applied            → Forge pushed a fix commit in this round
  forge:no-fix-needed          → Forge looked, decided no action
  forge:no-merge               → Forge / triage decided to close
```

Look at the latest comment for the actual analysis / reply text.
