Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
4ea33ad
feat(gitlab): add hello-world CI/CD template to validate include:remote
factory-nizar Jun 1, 2026
64a7870
feat(gitlab): scaffold src/gitlab adapter (context, token, api, types)
factory-nizar Jun 1, 2026
8176251
feat(gitlab): add MCP server exposing MR operations to droid exec
factory-nizar Jun 1, 2026
d5e7b34
feat(gitlab): add prepare + update-comment-link entrypoints and stick…
factory-nizar Jun 1, 2026
03ce317
feat(gitlab): real CI/CD Component template (runtime-clone, no Docker)
factory-nizar Jun 1, 2026
ab7c5f0
feat(gitlab): wire up real droid exec with MCP server + inline review
factory-nizar Jun 1, 2026
94bb5b5
fix(gitlab): wire review_depth presets via resolveReviewConfig
factory-nizar Jun 1, 2026
08a9b01
feat(gitlab): two-pass review (candidates + validator) mirroring GitHub
factory-nizar Jun 1, 2026
935519c
feat(gitlab): wire `settings` input pass-through (parity with GitHub)
factory-nizar Jun 1, 2026
7df3492
feat(gitlab): GH parity batch — custom droids, debug artifacts, telem…
factory-nizar Jun 2, 2026
efdd18b
feat(gitlab): expose include_suggestions input (A5)
factory-nizar Jun 2, 2026
3def7bc
feat(gitlab): cache bun install cache across pipelines (C8)
factory-nizar Jun 2, 2026
9466351
feat(gitlab): rate-limit + exponential backoff in GitlabClient (C1)
factory-nizar Jun 2, 2026
3ff7324
feat(gitlab): paginate listNotes + add listDiscussions (C2)
factory-nizar Jun 2, 2026
844768a
Revert "feat(gitlab): paginate listNotes + add listDiscussions (C2)"
factory-nizar Jun 2, 2026
18ab391
Revert "feat(gitlab): rate-limit + exponential backoff in GitlabClien…
factory-nizar Jun 2, 2026
46be9f7
Revert "feat(gitlab): cache bun install cache across pipelines (C8)"
factory-nizar Jun 2, 2026
c71725e
revert(gitlab): drop draft-skip rules + stderr tail-60 capture
factory-nizar Jun 2, 2026
08f5c4c
feat(gitlab): expose automatic_security_review input (A1, per-MR scope)
factory-nizar Jun 2, 2026
1c6a2a5
feat(gitlab): expose security_block_on_critical/high inputs (A10)
factory-nizar Jun 2, 2026
6f05311
docs(gitlab): add gitlab-setup.md and README section for GitLab CI/CD…
factory-nizar Jun 2, 2026
ecd3fbe
docs(gitlab): add drop-in .gitlab-ci.yml samples under gitlab/examples/
factory-nizar Jun 2, 2026
f1252df
docs(gitlab): hide internal inputs, trim setup.md, default ref to main
factory-nizar Jun 2, 2026
4338125
docs(readme): drop GitLab non-support note from quick-start
factory-nizar Jun 2, 2026
80e6581
docs(readme): tighten GitLab section wording
factory-nizar Jun 2, 2026
448087d
refactor(gitlab): rename template to droid-review.yml; ship as two-fi…
factory-nizar Jun 2, 2026
1e3e0aa
refactor(gitlab): move component to top-level templates/ for Catalog …
factory-nizar Jun 2, 2026
7561989
feat(gitlab): point at gitlab.com/factory-components/droid-action mirror
factory-nizar Jun 2, 2026
7ad303f
fix(gitlab): address bot review nits on #93
factory-nizar Jun 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,39 @@ The guided flow will:

For GitHub-only setups you can also run `/install-github-app`. See the [Automated Code Review guide](https://docs.factory.ai/guides/droid-exec/code-review) and the [GitHub App installation guide](https://docs.factory.ai/cli/features/install-github-app) for full details.

### GitLab

GitLab support ships as a **GitLab CI/CD Component** that delivers automated code review — inline MR comments on every merge request, with optional security review.

Two files in your project:

`factory/droid-review.yml`:

```yaml
include:
- project: "factory-components/droid-action"
ref: main
file: "/templates/droid-review.yml"
inputs:
automatic_review: "true"
automatic_security_review: "false"
review_depth: "deep"

droid-review:
variables:
FACTORY_API_KEY: $FACTORY_API_KEY
GITLAB_TOKEN: $GITLAB_TOKEN
```

`.gitlab-ci.yml` (one include line, append to existing if present):

```yaml
include:
- local: "factory/droid-review.yml"
```

Full setup, available inputs, and troubleshooting live in [`docs/gitlab-setup.md`](docs/gitlab-setup.md).

### Manual Setup

If you prefer to wire things up by hand:
Expand Down
106 changes: 106 additions & 0 deletions docs/gitlab-setup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# GitLab Setup

This action ships a **GitLab CI/CD Component** that delivers the same
automated code-review experience as the GitHub action on GitLab merge
requests (MRs). The component runs on every `merge_request_event` pipeline,
posts inline comments on the diff, maintains a sticky tracking note, and
optionally runs a security-focused subagent in parallel.

## Quick start with `/install-code-review`

The fastest path is the guided installer built into the Droid CLI:

```bash
droid
> /install-code-review
```

It detects GitLab, asks which account should be the poster of review
comments (you supply its PAT as `GITLAB_TOKEN`), asks the configuration
questions below, drops `factory/droid-review.yml` in your project, wires
it into `.gitlab-ci.yml`, and opens an MR / direct-commits to the target
project(s).

## Manual installation

### 1. Prerequisites

| Requirement | How to get it |
| ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| GitLab Maintainer role on the project | Repo admin grants you Maintainer (40) |
| `FACTORY_API_KEY` CI/CD variable | Generate at <https://app.factory.ai/settings/api-keys>; add as **masked**, **unprotected** variable at the project, subgroup, or top-level group level |
| `GITLAB_TOKEN` CI/CD variable | A personal access token with the `api` scope, owned by whichever account should post review comments. The token owner is the poster — there is no API impersonation. Add as **masked**, **unprotected**. |

### 2. Add the CI/CD Component

Drop-in samples live in [`gitlab/examples/`](../gitlab/examples/). The
layout is two files:

- [`factory/droid-review.yml`](../gitlab/examples/factory/droid-review.yml) — self-contained config (include + inputs + variables). Drop verbatim.
- [`.gitlab-ci.yml`](../gitlab/examples/.gitlab-ci.yml) — project-root entry point. If you already have one, append the include line below to its `include:` block.

**`factory/droid-review.yml`** (drop into your project):

```yaml
include:
- project: "factory-components/droid-action"
ref: main
file: "/templates/droid-review.yml"
inputs:
automatic_review: "true"
automatic_security_review: "false"
review_depth: "deep"
include_suggestions: "true"
security_block_on_critical: "true"
security_block_on_high: "false"

droid-review:
variables:
FACTORY_API_KEY: $FACTORY_API_KEY
GITLAB_TOKEN: $GITLAB_TOKEN
```

**`.gitlab-ci.yml`** (project root, just needs the one include line):

```yaml
include:
- local: "factory/droid-review.yml"
```

> The remote `include:` URL is pinned to `@main`, which tracks the
> latest stable cut of droid-action.

### 3. Push an MR

Open or push to an MR. The next `merge_request_event` pipeline will run
the `droid-review` job. Expect ~5-10 minutes for a typical change.

## Inputs

| Input | Default | Description |
| ---------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `automatic_review` | `"true"` | Run code review automatically on every MR pipeline. |
| `automatic_security_review` | `"false"` | Run a parallel security-focused subagent on every MR pipeline. Findings are prefixed `[security]` and posted alongside code-review comments. |
| `review_depth` | `"deep"` | `"deep"` (thorough) or `"shallow"` (fast). |
| `review_model` | `""` | Override the model. Empty = use depth preset. |
| `reasoning_effort` | `""` | Override reasoning effort. Empty = use depth preset. |
| `include_suggestions` | `"true"` | Include code suggestion blocks in review comments when the fix is high-confidence. |
| `security_block_on_critical` | `"true"` | Block merge on CRITICAL security findings. (Mirrors GitHub action; surface-level parity.) |
| `security_block_on_high` | `"false"` | Block merge on HIGH security findings. (Mirrors GitHub action; surface-level parity.) |
| `settings` | `""` | Droid Exec settings as a JSON string or a path to a JSON file. Merged into `~/.factory/droid/settings.json` before each `droid exec` call. |

## What you get

Each MR pipeline produces:

- **Inline review comments** anchored to the relevant diff lines, posted in a
single batched `submit_review` call. Findings are prefixed with priority
tags (`P0`, `P1`, `P2`, `P3`) and `[security]` for security findings.
- **A sticky tracking note** on the MR with pipeline + job links, telemetry
(`N turns • Xm Ys`), session IDs, and a security badge when
`automatic_security_review` is enabled.
- **Debug artifacts** at `.droid-debug/` (prompts, candidate JSON, raw
stream-json logs) retained for 1 week.
- **A custom droid library** copied from
`$DROID_ACTION_DIR/.factory/droids` into `~/.factory/droids` on the
runner, so subagents like `security-reviewer` are reachable.
9 changes: 9 additions & 0 deletions gitlab/examples/.gitlab-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Example project-root .gitlab-ci.yml.
#
# If the project doesn't have a .gitlab-ci.yml yet, create one with at
# minimum the include line below. If it already has one, just append the
# `- local: "factory/droid-review.yml"` entry to its existing `include:`
# block (or add a new `include:` block if there isn't one).

include:
- local: "factory/droid-review.yml"
21 changes: 21 additions & 0 deletions gitlab/examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# GitLab examples

Two-file layout for consuming the droid-action GitLab CI/CD Component:

```
your-project/
├── .gitlab-ci.yml # gains one `include:` line
└── factory/
└── droid-review.yml # self-contained droid-review config
```

| File | Where it lives in your project | Purpose |
| ----------------------------- | -------------------------------------------------- | --------------------------------------------------------------------------------------------- |
| `factory/droid-review.yml` | drop verbatim | Self-contained config: includes the remote Component, sets inputs, wires CI/CD variables. |
| `.gitlab-ci.yml` | append one `include:` line if the file exists | Project-root entry point. Just needs to include `factory/droid-review.yml`. |

The two required CI/CD variables (`FACTORY_API_KEY`, `GITLAB_TOKEN`) are set
in the GitLab UI under **Project → Settings → CI/CD → Variables** (or at
the group level for org-wide rollout), masked and unprotected.

For the full input reference see the docs at [`docs/gitlab-setup.md`](../../docs/gitlab-setup.md).
31 changes: 31 additions & 0 deletions gitlab/examples/factory/droid-review.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# factory/droid-review.yml
#
# Drop this file at `factory/droid-review.yml` in your project, then
# add a single `include: - local: "factory/droid-review.yml"` line to
# your `.gitlab-ci.yml` (see ../.gitlab-ci.yml for the entry-point
# example).
#
# Prerequisites (one-time, set in Project / Group → Settings → CI/CD):
#
# * FACTORY_API_KEY — masked variable. Get one at
# https://app.factory.ai/settings/api-keys
# * GITLAB_TOKEN — masked variable. A personal access token with
# `api` scope, owned by whichever GitLab account should post review
# comments. The token owner IS the poster.

include:
- project: "factory-components/droid-action"
ref: main
file: "/templates/droid-review.yml"
inputs:
automatic_review: "true" # run on every MR event; "false" disables
review_depth: "deep" # "deep" (thorough) or "shallow" (fast)
include_suggestions: "true" # post code-suggestion blocks for high-confidence fixes
automatic_security_review: "false" # enable to flag vulns alongside the regular review
security_block_on_critical: "true" # block merge on CRITICAL findings (when security review is on)
security_block_on_high: "false" # block merge on HIGH findings

droid-review:
variables:
FACTORY_API_KEY: $FACTORY_API_KEY
GITLAB_TOKEN: $GITLAB_TOKEN
97 changes: 97 additions & 0 deletions src/entrypoints/gitlab-prepare-validator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#!/usr/bin/env bun

/**
* Prepare step for Pass 2 (validator) of the GitLab two-pass review flow.
*
* Runs between the two `droid exec` invocations in the CI template:
*
* 1. Reads the state file produced by `gitlab-prepare.ts` (Pass 1).
* Bails out cleanly if Pass 1 decided not to review.
* 2. Reconstructs the GitLab review prompt context from state.
* 3. Generates the Pass-2 validator prompt.
* 4. Overwrites the shared prompt file (the same file Pass 1 used)
* so the next `droid exec -f <promptPath>` consumes Pass 2.
*
* No GitLab API calls are made here — all the data we need is already
* on disk from Pass 1's artifact precomputation.
*/

import * as fs from "fs/promises";
import * as path from "path";
import { generateGitlabReviewValidatorPrompt } from "../gitlab/prompts/validator";
import type { GitlabReviewPromptContext } from "../gitlab/prompts/types";
import type { PrepareState } from "./gitlab-prepare";

function stateFilePath(): string {
return (
process.env.DROID_STATE_FILE ||
path.join(process.env.CI_PROJECT_DIR || "/tmp", ".droid-state.json")
);
}

async function readState(): Promise<PrepareState> {
const filePath = stateFilePath();
const raw = await fs.readFile(filePath, "utf8");
return JSON.parse(raw) as PrepareState;
}

function ensure<T>(value: T | null | undefined, name: string): T {
if (value === null || value === undefined) {
throw new Error(
`gitlab-prepare-validator: missing state.${name}; was gitlab-prepare run successfully?`,
);
}
return value;
}

async function run(): Promise<void> {
const state = await readState();

if (!state.shouldRunReview) {
console.log(
`Pass 1 was skipped (reason: ${state.reason ?? "unknown"}); skipping validator prepare.`,
);
return;
}

const promptPath = ensure(state.promptPath, "promptPath");
const mrIid = ensure(state.mrIid, "mrIid");
const candidatesPath = ensure(state.candidatesPath, "candidatesPath");
const validatedPath = ensure(state.validatedPath, "validatedPath");
const diffPath = ensure(state.diffPath, "diffPath");
const commentsPath = ensure(state.commentsPath, "commentsPath");
const descriptionPath = ensure(state.descriptionPath, "descriptionPath");
const headSha = ensure(state.headSha, "headSha");

const promptCtx: GitlabReviewPromptContext = {
projectPath: state.projectPath,
mrIid,
mrTitle: state.mrTitle ?? "",
sourceBranch: state.sourceBranch ?? "",
targetBranch: state.targetBranch ?? "",
headSha,
diffPath,
commentsPath,
descriptionPath,
candidatesPath,
validatedPath,
includeSuggestions: state.includeSuggestions,
securityReviewEnabled: state.securityReviewEnabled,
};

const prompt = generateGitlabReviewValidatorPrompt(promptCtx);
await fs.mkdir(path.dirname(promptPath), { recursive: true });
await fs.writeFile(promptPath, prompt);
console.log(
`Wrote Pass-2 validator prompt (${prompt.length} bytes) to ${promptPath} (overwrote Pass 1)`,
);
}

if (import.meta.main) {
run().catch((error) => {
console.error("gitlab-prepare-validator failed:", error);
process.exit(1);
});
}

export { run };
Loading