Skip to content

Commit 84946f2

Browse files
feat(gitlab): Phase 1 GitLab support via CI/CD Component (#93)
* feat(gitlab): add hello-world CI/CD template to validate include:remote Adds a minimal gitlab/templates/review.yml job that prints a hello-world message. Used to verify that a GitLab CI pipeline can pull the template over HTTPS from raw.githubusercontent.com before we layer in the Docker image and real review entrypoints. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> * feat(gitlab): scaffold src/gitlab adapter (context, token, api, types) Pure-addition first slice of GitLab support. Mirrors the shape of src/github/ so subsequent entrypoints can swap adapter by PLATFORM env. - src/gitlab/context.ts parses CI_* env vars into a normalized ParsedGitlabContext with MR + commit + user + inputs - src/gitlab/token.ts reads GITLAB_TOKEN with OVERRIDE/CI_JOB_TOKEN fallbacks; throws a clear error when missing - src/gitlab/api/client.ts is a thin fetch wrapper around GitLab v4 exposing getMr/getMrChanges/listNotes/createNote/updateNote/ createDiscussionOnDiff/updateMrDescription - src/gitlab/types.ts defines GitlabMr, GitlabNote, GitlabDiscussion, GitlabPosition - 16 new unit tests; full existing suite (381 -> 397) still passes No GitHub code paths touched. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> * feat(gitlab): add MCP server exposing MR operations to droid exec src/mcp/gitlab-mr-server.ts mirrors src/mcp/github-pr-server.ts in shape so the review prompt can stay platform-agnostic at the tool layer. Tools registered: - get_mr / get_mr_changes / list_mr_notes - create_mr_note / update_mr_note (sticky tracking comment lifecycle) - update_mr_description (for @droid fill) - submit_review: posts an optional summary note plus N inline discussions anchored to diff positions; per-comment errors are collected so one bad position doesn't abort the batch. Position objects are built from the MR's diff_refs (base/head/start SHA) and switch new_line vs old_line based on RIGHT vs LEFT side, matching the GitHub PR review tool's contract. 5 new unit tests cover summary, inline anchoring, LEFT-side mapping, and partial-failure batching. Full suite: 402/402. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> * feat(gitlab): add prepare + update-comment-link entrypoints and sticky-note logic Implements the sticky tracking note lifecycle for GitLab MR pipelines: prepare creates (or reuses) the running-state note before droid exec runs; update-comment-link rewrites it with success/failure + pipeline links after droid exec completes. - src/gitlab/operations/tracking-note.ts builds the note body with a hidden <!-- droid-tracking-note --> marker so retries find and update the same note instead of creating duplicates. Renders pipeline/job links, security badge (when automatic_security_review is on), and an error-details <details> block on failure. - src/entrypoints/gitlab-prepare.ts parses CI env, gates on merge_request_event + automatic_review, writes a JSON state file (DROID_STATE_FILE) for downstream steps. - src/entrypoints/gitlab-update-comment-link.ts reads the state file and PUTs the final body. Skips cleanly when no review ran. 7 new tracking-note tests; full suite 402 -> 409. Also tightened two gitlab-mr-server tests to satisfy noUncheckedIndexedAccess. Cross-SCM include:remote was validated end-to-end against gitlab.com pipeline #2568334623 (passed) before this commit. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> * feat(gitlab): real CI/CD Component template (runtime-clone, no Docker) Replaces the hello-world include-test template with a real droid-review job. No Docker image dependency for v1; instead the job: 1. Uses the public oven/bun:1.2.11 image (Bun preinstalled) 2. apt-get installs git/curl/ca-certificates on top 3. Shallow-clones Factory-AI/droid-action at $DROID_ACTION_REF 4. bun install --frozen-lockfile 5. Runs src/entrypoints/gitlab-prepare.ts (creates sticky tracking note) 6. droid exec placeholder (real prompt + MCP wiring next commit) 7. after_script always runs src/entrypoints/gitlab-update-comment-link.ts so failures still rewrite the note to the failure state Template uses GitLab's spec.inputs so it works equally well via `include: component:` (post-Catalog publish) and `include: remote:` (today). Inputs: automatic_review, review_depth, review_model, reasoning_effort, droid_action_repo, droid_action_ref, stage. Required CI variables on the consumer side: FACTORY_API_KEY, GITLAB_TOKEN. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> * feat(gitlab): wire up real droid exec with MCP server + inline review The template now installs the Droid CLI, registers the gitlab-mr MCP server with the runtime CI env, and runs `droid exec` against a review prompt that gitlab-prepare writes to /tmp/droid-prompts/droid-prompt.txt. - src/gitlab/review-prompt.ts: minimal v1 review prompt builder embedding the MR diff and instructions to call submit_review with a single batched set of inline comments (max 8). Returns LGTM body when no issues found. - src/entrypoints/gitlab-prepare.ts: after creating the sticky note, fetches MR + diff via the GitLab API and writes the prompt file; exposes promptPath on the state JSON. - gitlab/templates/review.yml: * Installs Droid CLI via curl https://app.factory.ai/cli | sh * Skips droid exec if prepare set shouldRunReview=false * Registers MCP server: droid mcp add gitlab_mr <bun run server> --env ... * Runs: droid exec -f $DROID_PROMPT_FILE --output-format stream-json --skip-permissions-unsafe (plus optional --model/--reasoning-effort) * after_script reads /tmp/droid-error.txt for failure details - 3 new prompt-builder tests; suite at 412 passing. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> * fix(gitlab): wire review_depth presets via resolveReviewConfig Previously, review_depth was a declared spec.input that was parsed and threaded through gitlab-prepare but never consumed by the prompt builder or the template -- so customers passing review_depth: shallow silently still got Droid's default model. This wires it end-to-end: - gitlab-prepare now calls resolveReviewConfig({reviewModel, reasoningEffort, reviewDepth}) from src/utils/review-depth.ts (which already had the preset table: shallow -> kimi-k2-0711, deep -> gpt-5.2 + high reasoning), reusing the same logic the GitHub flow uses. - Explicit review_model / reasoning_effort still beat the depth preset (resolveReviewConfig handles priority). - Resolved values are written to /tmp/droid-prompts/resolved-env.sh as shell exports (RESOLVED_MODEL / RESOLVED_REASONING_EFFORT), plus echoed into the state.json for visibility. - Template sources the shim before constructing droid exec args and uses the RESOLVED_* values for --model / --reasoning-effort. Adds 6 unit tests covering: default deep preset, shallow preset, explicit model override, explicit effort override, both overrides simultaneously, unknown depth fallback. Suite at 418 passing. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> * feat(gitlab): two-pass review (candidates + validator) mirroring GitHub Replaces the v1 single-pass bespoke prompt with the same two-pass flow the GitHub Action uses: Pass 1 generates review_candidates.json without posting; Pass 2 validates each candidate, writes review_validated.json, and submits approved findings in a single batched call. The /review skill (loaded by Droid at runtime) drives both passes; the prompts here are the runtime harness that selects which pass + which tools. Why two droid exec calls rather than one: 1. Tool gating — Pass 1 has NO access to gitlab_mr___submit_review so the model can't shortcut by posting raw candidates. --enabled-tools is fixed at process start, so a single exec can't switch mid-run. 2. Fresh-eyes context — Pass 2 re-reads the diff without memory of why each candidate was generated, dropping ~30-60% false positives. 3. Hard output checkpoint on disk between passes (resumable, debuggable). 4. Clean failure semantics — half-baked Pass 1 won't pollute Pass 2. New files: * src/gitlab/data/review-artifacts.ts — fetch + write mr.diff, existing_comments.json, mr_description.txt (mirror of GitHub's precomputed PR artifacts). * src/gitlab/prompts/types.ts — shared prompt-context shape. * src/gitlab/prompts/candidates.ts — Pass 1 prompt, ported from GitHub's review-candidates-prompt.ts with MR/project terminology. * src/gitlab/prompts/validator.ts — Pass 2 prompt, ported from GitHub's review-validator-prompt.ts; uses gitlab_mr___submit_review and the new gitlab_mr___update_tracking_note tool. * src/entrypoints/gitlab-prepare-validator.ts — overwrites the prompt file with Pass 2 between the two droid exec calls. * test/gitlab/prompts.test.ts + test/gitlab/review-artifacts.test.ts — 14 new tests covering the two prompts and artifact computation. MCP additions: * src/mcp/gitlab-mr-server.ts — new update_tracking_note tool that reads DROID_MR_IID + DROID_TRACKING_NOTE_ID from env, symmetric with GitHub's github_comment___update_droid_comment. Template changes (gitlab/templates/review.yml): * Sources resolved-env.sh once up front (now includes DROID_MR_IID + DROID_TRACKING_NOTE_ID). * Registers the MCP server with those env vars exposed. * Pass 1 droid exec: --enabled-tools excludes submit_review. * Runs gitlab-prepare-validator between the two execs. * Pass 2 droid exec: --enabled-tools includes gitlab_mr___submit_review and gitlab_mr___update_tracking_note. * Artifacts archive bumped to include review_candidates.json, review_validated.json, droid-prompt.txt for debugging. Removed: * src/gitlab/review-prompt.ts (v1 single-pass bespoke prompt). * test/gitlab/review-prompt.test.ts. Refactoring src/gitlab/prompts/ + src/create-prompt/ into a shared src/core/review/ tree is queued as a follow-up PR. All 432 tests pass; tsc + prettier clean. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> * feat(gitlab): wire `settings` input pass-through (parity with GitHub) Adds the same `settings` input the GitHub action exposes: * spec.input on the CI/CD Component (JSON string OR path to JSON file). * Plumbed via DROID_SETTINGS env var to gitlab-prepare. * gitlab-prepare reuses base-action/src/setup-droid-settings.ts (zero GitHub deps, fully portable) to write ~/.factory/droid/settings.json before either droid exec call. Always sets enableAllProjectMcpServers=true to match the GitHub flow. * context.inputs.settings surfaces the raw value. * 2 new context tests; 434 tests pass. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> * feat(gitlab): GH parity batch — custom droids, debug artifacts, telemetry, draft skip, stderr capture - 1.6b Copy bundled .factory/droids from droid-action checkout into runner ~/.factory/droids before droid exec, mirroring the GitHub action's Setup Custom Droids step. - 1.6c Stage debug artifacts (/tmp/droid-prompts/*, droid-error.txt) into .droid-debug/ inside CI_PROJECT_DIR in after_script so GitLab can upload them on failure (GitLab artifacts cannot reach outside CI_PROJECT_DIR). - 1.6d Capture droid exec stream-json output to per-pass JSONL log files via tee + pipefail; parse session_id / numTurns / durationMs (+ legacy cost_usd) from the last completion event of each pass; render a <sub> summary line (44 turns • 11m 40s • $0.42) and a collapsible session-IDs block in the sticky tracking note. - C7 Skip Draft: / WIP: MRs at the rules: level (GitLab-native equivalent of GitHub's draft == false workflow if:). - C9 On droid exec failure, tail 60 lines of the per-pass log into /tmp/droid-error.txt so the existing error-details block in the sticky note surfaces real failure context instead of a generic message. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> * feat(gitlab): expose include_suggestions input (A5) Adds the include_suggestions spec input to the GitLab review template. Threading was already in place: gitlab-prepare reads the INCLUDE_SUGGESTIONS env var, stores it in the on-disk state, gitlab-prepare-validator restores it, and both candidate + validator prompts already consume includeSuggestions to gate suggestion-block guidance. This commit just exposes the toggle as a customer-facing input. Mirrors the GitHub action's include_suggestions input. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> * feat(gitlab): cache bun install cache across pipelines (C8) Adds a cache: block keyed on droid_action_ref so bun's package cache survives across MR pipelines. BUN_INSTALL_CACHE_DIR is pinned to CI_PROJECT_DIR/.bun-cache because GitLab's cache mechanism can only persist paths inside the project directory. policy: pull-push lets every job benefit from prior downloads and contribute newly-fetched packages back to the cache. Mirrors the actions/cache pattern in the GitHub action. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> * feat(gitlab): rate-limit + exponential backoff in GitlabClient (C1) Wraps every GitlabClient.request call with retry-on-transient-failure logic, mirroring the implicit retry behaviour the GitHub action gets from Octokit. Retries are limited to a configurable maxRetries (default 5) for: 408, 429, 500, 502, 503, 504, and raw fetch network errors. On 429 the client honours the Retry-After header when present (seconds or HTTP-date), and otherwise uses exponential backoff with jitter clamped to maxDelayMs. Non-retryable 4xx errors (401, 403, 404, etc.) surface immediately as GitlabApiError. The retry knobs are exposed via a third constructor argument so tests can use sub-millisecond delays without needing fake timers. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> * feat(gitlab): paginate listNotes + add listDiscussions (C2) Adds a private paginate<T>() helper that follows GitLab's standard pagination either via the X-Next-Page header (newer GitLab) or Link rel="next" (older GitLab + GitLab.com responses) with per_page=100 to minimise round-trips. listNotes now uses it and a new listDiscussions method is exposed for use cases that need to inspect existing diff discussions. Mirrors Octokit's implicit paginate-everything behaviour that the GitHub action gets for free. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> * Revert "feat(gitlab): paginate listNotes + add listDiscussions (C2)" This reverts commit 7c721d1. * Revert "feat(gitlab): rate-limit + exponential backoff in GitlabClient (C1)" This reverts commit d70dbd7. * Revert "feat(gitlab): cache bun install cache across pipelines (C8)" This reverts commit 0d6c73b. * revert(gitlab): drop draft-skip rules + stderr tail-60 capture Removes two GitLab-only behaviours that don't exist in the GitHub action: - Draft/WIP `rules: when: never` block. GitHub leaves draft handling to the user's workflow `if: ... draft == false` condition and doesn't bake it into the action. Customers who want to skip drafts can add the same condition to their own .gitlab-ci.yml. - Stderr tail-60 capture into droid-error.txt. GitHub uses core.setFailed(error.message) which surfaces in the Actions run summary, not in the PR comment body — the comment just shows an actionFailed flag. Revert to the plain "droid exec pass N failed" message for parity. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> * feat(gitlab): expose automatic_security_review input (A1, per-MR scope) Wires the automatic_security_review toggle through the GitLab CI/CD Component. When set to "true", the security-reviewer subagent already bundled in .factory/droids/security-reviewer.md is spawned in parallel with the code-review subagents during Pass 1 (the conditional block in src/gitlab/prompts/candidates.ts already handles this via the securityReviewEnabled flag). Security findings are prefixed with [security] and flow through the same Pass-2 validator path as code-review findings, ultimately posted via a single batched submit_review call. No plugin install is required: the security-review skill that the subagent invokes as its first action is built into the Droid CLI binary. Mirrors the GitHub action's automatic_security_review input for the per-MR flow only; the scheduled full-repo scan (which additionally uses threat-model-generation / commit-security-scan / vulnerability-validation skills from the security-engineer plugin) is a separate feature. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> * feat(gitlab): expose security_block_on_critical/high inputs (A10) Matches GitHub action's input surface for security_block_on_critical (default "true") and security_block_on_high (default "false"). Like the GitHub action, the inputs are parsed into context but not currently consumed by production code — they exist for surface-level parity and forward compatibility when blocking logic lands. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> * docs(gitlab): add gitlab-setup.md and README section for GitLab CI/CD Component Documents the manual installation flow for the new GitLab CI/CD Component (`gitlab/templates/review.yml`): prerequisites, full inputs table, what the pipeline produces, how the two-pass review works, what is intentionally not yet supported (comment triggers, fill mode, scheduled scans), and troubleshooting steps. README now has a short GitLab section that points to the full guide. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> * docs(gitlab): add drop-in .gitlab-ci.yml samples under gitlab/examples/ Adds two consumer-facing sample files plus a README so users have a canonical reference for what a Component-consuming .gitlab-ci.yml looks like: * gitlab/examples/.gitlab-ci.minimal.yml — shortest possible, every default accepted, only the two required CI/CD variables wired. * gitlab/examples/.gitlab-ci.example.yml — annotated, every input spelled out with safe-default guidance, plus an optional @droid fill block (commented out) showing how to add fill. * gitlab/examples/README.md — table mapping each file to its use case and the variable-setup story. Also updates docs/gitlab-setup.md: * Points readers at gitlab/examples/ instead of just inlining the snippet, so customers can clone the project, copy the file, done. * Drops the stale 'service account provisioning' reference now that the install-code-review skill no longer provisions SAs. * Tightens the GITLAB_TOKEN row to make the poster-identity story explicit: the token owner IS the poster, no API impersonation. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> * docs(gitlab): hide internal inputs, trim setup.md, default ref to main Customer-facing surface cleanup before GA: * gitlab/templates/review.yml: change droid_action_ref default from "dev" to "main" so customers who don't set it track stable. Description annotated 'Internal: most users leave at the default'. * docs/gitlab-setup.md: drop droid_action_ref, droid_action_repo, stage from the Inputs table — they're for self-hosted GitLab mirrors / advanced overrides, not standard customer config. * docs/gitlab-setup.md: drop everything from 'How it works' onward (How it works, What's not yet supported, Troubleshooting, Self-hosted GitLab). Those belong in deeper docs / runbooks; the setup page should be exactly setup. * docs/gitlab-setup.md: drop the 'pin to a release tag (e.g. v1)' advice — droid-action doesn't tag yet, and the @main URL pin is the canonical pattern. * gitlab/examples/.gitlab-ci.example.yml: drop droid_action_ref from the inputs block + drop the same line from the optional fill snippet. Update the comment to just explain the @main pin. * gitlab/examples/README.md: drop the 'custom stage' reference now that stage isn't documented as a user-facing knob. The hidden inputs still exist in the template with sensible defaults, so air-gapped customers mirroring droid-action can still override droid_action_repo or pin droid_action_ref to a private SHA — they just aren't part of the documented surface. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> * docs(readme): drop GitLab non-support note from quick-start Customer-facing surface should describe what is supported, not what isn't. Caveats belong in deeper docs / runbooks if anywhere, not in the README quick-start. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> * docs(readme): tighten GitLab section wording Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> * refactor(gitlab): rename template to droid-review.yml; ship as two-file pattern Component template renamed from gitlab/templates/review.yml to gitlab/templates/droid-review.yml. Customer drops a self-contained factory/droid-review.yml in their project and adds a single `include: - local:` line to their .gitlab-ci.yml, mirroring the GitHub action's `.github/workflows/droid-review.yml` model. Examples restructured to reflect the two-file layout. Docs + README snippets updated accordingly. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> * refactor(gitlab): move component to top-level templates/ for Catalog compatibility GitLab CI/CD Catalog requires components to live at top-level templates/. Move gitlab/templates/droid-review.yml to templates/droid-review.yml so the repo is ready for Catalog publish (gitlab.com/factory-ai/droid-action) without future renames. Add templates/README.md explaining the directory ownership (mandated by Catalog, parallel to action.yml + .github/workflows/ for GitHub) and a headline comment in droid-review.yml. Update all existing include URLs (README, docs, examples) to the new path. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> * feat(gitlab): point at gitlab.com/factory-components/droid-action mirror Switch the customer-facing include form from a raw GitHub URL to GitLab's native include: project: form pointing at the gitlab.com pull-mirror of this repo. Customers no longer need github.com egress to fetch the template — gitlab.com is sufficient. Also flip the runtime droid_action_repo default from github.com/Factory-AI/droid-action.git to gitlab.com/factory-components/droid-action.git so the at-job clone of the runtime source also goes through gitlab.com. Customers behind a firewall that only permits gitlab.com + app.factory.ai can now run the full flow. Updated: - README.md GitLab quick-start snippet (include: project: form) - docs/gitlab-setup.md Step 2 snippet - gitlab/examples/factory/droid-review.yml - templates/README.md — documents include: project: (default), future include: component: form once Catalog-tagged, and a raw GitHub URL fallback for projects that can't reach gitlab.com - templates/droid-review.yml headline comment + droid_action_repo default Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> * fix(gitlab): address bot review nits on #93 - templates/droid-review.yml: remove top-level `stages:` block so the Component doesn't leak its stage list into the consuming project. Job-level `stage: $[[ inputs.stage ]]` is unchanged; consumers add the stage to their own `stages:` list (or omit it for the default). - templates/droid-review.yml: tighten droid_action_ref docstring to "tag/branch" since the clone uses `git clone --branch` (SHAs would silently fail). - src/gitlab/token.ts: drop CI_JOB_TOKEN fallback. It was always set in CI so the MissingGitlabTokenError never fired, producing confusing 401s on first API call instead of a clear "set GITLAB_TOKEN" message. CI_JOB_TOKEN also lacks scopes for notes/discussions. - src/mcp/gitlab-mr-server.ts: require a line anchor on ReviewComment (line for RIGHT, old_line for LEFT) via .refine(); GitLab rejects unanchored diff discussions. - src/mcp/gitlab-mr-server.ts: remove `.max(30)` cap on comments so large MRs don't fail the entire submit_review call. Tests: 445/445 pass; tsc clean. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --------- Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
1 parent d74a29d commit 84946f2

31 files changed

Lines changed: 3506 additions & 0 deletions

README.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,39 @@ The guided flow will:
3838

3939
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.
4040

41+
### GitLab
42+
43+
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.
44+
45+
Two files in your project:
46+
47+
`factory/droid-review.yml`:
48+
49+
```yaml
50+
include:
51+
- project: "factory-components/droid-action"
52+
ref: main
53+
file: "/templates/droid-review.yml"
54+
inputs:
55+
automatic_review: "true"
56+
automatic_security_review: "false"
57+
review_depth: "deep"
58+
59+
droid-review:
60+
variables:
61+
FACTORY_API_KEY: $FACTORY_API_KEY
62+
GITLAB_TOKEN: $GITLAB_TOKEN
63+
```
64+
65+
`.gitlab-ci.yml` (one include line, append to existing if present):
66+
67+
```yaml
68+
include:
69+
- local: "factory/droid-review.yml"
70+
```
71+
72+
Full setup, available inputs, and troubleshooting live in [`docs/gitlab-setup.md`](docs/gitlab-setup.md).
73+
4174
### Manual Setup
4275

4376
If you prefer to wire things up by hand:

docs/gitlab-setup.md

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
# GitLab Setup
2+
3+
This action ships a **GitLab CI/CD Component** that delivers the same
4+
automated code-review experience as the GitHub action on GitLab merge
5+
requests (MRs). The component runs on every `merge_request_event` pipeline,
6+
posts inline comments on the diff, maintains a sticky tracking note, and
7+
optionally runs a security-focused subagent in parallel.
8+
9+
## Quick start with `/install-code-review`
10+
11+
The fastest path is the guided installer built into the Droid CLI:
12+
13+
```bash
14+
droid
15+
> /install-code-review
16+
```
17+
18+
It detects GitLab, asks which account should be the poster of review
19+
comments (you supply its PAT as `GITLAB_TOKEN`), asks the configuration
20+
questions below, drops `factory/droid-review.yml` in your project, wires
21+
it into `.gitlab-ci.yml`, and opens an MR / direct-commits to the target
22+
project(s).
23+
24+
## Manual installation
25+
26+
### 1. Prerequisites
27+
28+
| Requirement | How to get it |
29+
| ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
30+
| GitLab Maintainer role on the project | Repo admin grants you Maintainer (40) |
31+
| `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 |
32+
| `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**. |
33+
34+
### 2. Add the CI/CD Component
35+
36+
Drop-in samples live in [`gitlab/examples/`](../gitlab/examples/). The
37+
layout is two files:
38+
39+
- [`factory/droid-review.yml`](../gitlab/examples/factory/droid-review.yml) — self-contained config (include + inputs + variables). Drop verbatim.
40+
- [`.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.
41+
42+
**`factory/droid-review.yml`** (drop into your project):
43+
44+
```yaml
45+
include:
46+
- project: "factory-components/droid-action"
47+
ref: main
48+
file: "/templates/droid-review.yml"
49+
inputs:
50+
automatic_review: "true"
51+
automatic_security_review: "false"
52+
review_depth: "deep"
53+
include_suggestions: "true"
54+
security_block_on_critical: "true"
55+
security_block_on_high: "false"
56+
57+
droid-review:
58+
variables:
59+
FACTORY_API_KEY: $FACTORY_API_KEY
60+
GITLAB_TOKEN: $GITLAB_TOKEN
61+
```
62+
63+
**`.gitlab-ci.yml`** (project root, just needs the one include line):
64+
65+
```yaml
66+
include:
67+
- local: "factory/droid-review.yml"
68+
```
69+
70+
> The remote `include:` URL is pinned to `@main`, which tracks the
71+
> latest stable cut of droid-action.
72+
73+
### 3. Push an MR
74+
75+
Open or push to an MR. The next `merge_request_event` pipeline will run
76+
the `droid-review` job. Expect ~5-10 minutes for a typical change.
77+
78+
## Inputs
79+
80+
| Input | Default | Description |
81+
| ---------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
82+
| `automatic_review` | `"true"` | Run code review automatically on every MR pipeline. |
83+
| `automatic_security_review` | `"false"` | Run a parallel security-focused subagent on every MR pipeline. Findings are prefixed `[security]` and posted alongside code-review comments. |
84+
| `review_depth` | `"deep"` | `"deep"` (thorough) or `"shallow"` (fast). |
85+
| `review_model` | `""` | Override the model. Empty = use depth preset. |
86+
| `reasoning_effort` | `""` | Override reasoning effort. Empty = use depth preset. |
87+
| `include_suggestions` | `"true"` | Include code suggestion blocks in review comments when the fix is high-confidence. |
88+
| `security_block_on_critical` | `"true"` | Block merge on CRITICAL security findings. (Mirrors GitHub action; surface-level parity.) |
89+
| `security_block_on_high` | `"false"` | Block merge on HIGH security findings. (Mirrors GitHub action; surface-level parity.) |
90+
| `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. |
91+
92+
## What you get
93+
94+
Each MR pipeline produces:
95+
96+
- **Inline review comments** anchored to the relevant diff lines, posted in a
97+
single batched `submit_review` call. Findings are prefixed with priority
98+
tags (`P0`, `P1`, `P2`, `P3`) and `[security]` for security findings.
99+
- **A sticky tracking note** on the MR with pipeline + job links, telemetry
100+
(`N turns • Xm Ys`), session IDs, and a security badge when
101+
`automatic_security_review` is enabled.
102+
- **Debug artifacts** at `.droid-debug/` (prompts, candidate JSON, raw
103+
stream-json logs) retained for 1 week.
104+
- **A custom droid library** copied from
105+
`$DROID_ACTION_DIR/.factory/droids` into `~/.factory/droids` on the
106+
runner, so subagents like `security-reviewer` are reachable.

gitlab/examples/.gitlab-ci.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Example project-root .gitlab-ci.yml.
2+
#
3+
# If the project doesn't have a .gitlab-ci.yml yet, create one with at
4+
# minimum the include line below. If it already has one, just append the
5+
# `- local: "factory/droid-review.yml"` entry to its existing `include:`
6+
# block (or add a new `include:` block if there isn't one).
7+
8+
include:
9+
- local: "factory/droid-review.yml"

gitlab/examples/README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# GitLab examples
2+
3+
Two-file layout for consuming the droid-action GitLab CI/CD Component:
4+
5+
```
6+
your-project/
7+
├── .gitlab-ci.yml # gains one `include:` line
8+
└── factory/
9+
└── droid-review.yml # self-contained droid-review config
10+
```
11+
12+
| File | Where it lives in your project | Purpose |
13+
| ----------------------------- | -------------------------------------------------- | --------------------------------------------------------------------------------------------- |
14+
| `factory/droid-review.yml` | drop verbatim | Self-contained config: includes the remote Component, sets inputs, wires CI/CD variables. |
15+
| `.gitlab-ci.yml` | append one `include:` line if the file exists | Project-root entry point. Just needs to include `factory/droid-review.yml`. |
16+
17+
The two required CI/CD variables (`FACTORY_API_KEY`, `GITLAB_TOKEN`) are set
18+
in the GitLab UI under **Project → Settings → CI/CD → Variables** (or at
19+
the group level for org-wide rollout), masked and unprotected.
20+
21+
For the full input reference see the docs at [`docs/gitlab-setup.md`](../../docs/gitlab-setup.md).
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# factory/droid-review.yml
2+
#
3+
# Drop this file at `factory/droid-review.yml` in your project, then
4+
# add a single `include: - local: "factory/droid-review.yml"` line to
5+
# your `.gitlab-ci.yml` (see ../.gitlab-ci.yml for the entry-point
6+
# example).
7+
#
8+
# Prerequisites (one-time, set in Project / Group → Settings → CI/CD):
9+
#
10+
# * FACTORY_API_KEY — masked variable. Get one at
11+
# https://app.factory.ai/settings/api-keys
12+
# * GITLAB_TOKEN — masked variable. A personal access token with
13+
# `api` scope, owned by whichever GitLab account should post review
14+
# comments. The token owner IS the poster.
15+
16+
include:
17+
- project: "factory-components/droid-action"
18+
ref: main
19+
file: "/templates/droid-review.yml"
20+
inputs:
21+
automatic_review: "true" # run on every MR event; "false" disables
22+
review_depth: "deep" # "deep" (thorough) or "shallow" (fast)
23+
include_suggestions: "true" # post code-suggestion blocks for high-confidence fixes
24+
automatic_security_review: "false" # enable to flag vulns alongside the regular review
25+
security_block_on_critical: "true" # block merge on CRITICAL findings (when security review is on)
26+
security_block_on_high: "false" # block merge on HIGH findings
27+
28+
droid-review:
29+
variables:
30+
FACTORY_API_KEY: $FACTORY_API_KEY
31+
GITLAB_TOKEN: $GITLAB_TOKEN
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
#!/usr/bin/env bun
2+
3+
/**
4+
* Prepare step for Pass 2 (validator) of the GitLab two-pass review flow.
5+
*
6+
* Runs between the two `droid exec` invocations in the CI template:
7+
*
8+
* 1. Reads the state file produced by `gitlab-prepare.ts` (Pass 1).
9+
* Bails out cleanly if Pass 1 decided not to review.
10+
* 2. Reconstructs the GitLab review prompt context from state.
11+
* 3. Generates the Pass-2 validator prompt.
12+
* 4. Overwrites the shared prompt file (the same file Pass 1 used)
13+
* so the next `droid exec -f <promptPath>` consumes Pass 2.
14+
*
15+
* No GitLab API calls are made here — all the data we need is already
16+
* on disk from Pass 1's artifact precomputation.
17+
*/
18+
19+
import * as fs from "fs/promises";
20+
import * as path from "path";
21+
import { generateGitlabReviewValidatorPrompt } from "../gitlab/prompts/validator";
22+
import type { GitlabReviewPromptContext } from "../gitlab/prompts/types";
23+
import type { PrepareState } from "./gitlab-prepare";
24+
25+
function stateFilePath(): string {
26+
return (
27+
process.env.DROID_STATE_FILE ||
28+
path.join(process.env.CI_PROJECT_DIR || "/tmp", ".droid-state.json")
29+
);
30+
}
31+
32+
async function readState(): Promise<PrepareState> {
33+
const filePath = stateFilePath();
34+
const raw = await fs.readFile(filePath, "utf8");
35+
return JSON.parse(raw) as PrepareState;
36+
}
37+
38+
function ensure<T>(value: T | null | undefined, name: string): T {
39+
if (value === null || value === undefined) {
40+
throw new Error(
41+
`gitlab-prepare-validator: missing state.${name}; was gitlab-prepare run successfully?`,
42+
);
43+
}
44+
return value;
45+
}
46+
47+
async function run(): Promise<void> {
48+
const state = await readState();
49+
50+
if (!state.shouldRunReview) {
51+
console.log(
52+
`Pass 1 was skipped (reason: ${state.reason ?? "unknown"}); skipping validator prepare.`,
53+
);
54+
return;
55+
}
56+
57+
const promptPath = ensure(state.promptPath, "promptPath");
58+
const mrIid = ensure(state.mrIid, "mrIid");
59+
const candidatesPath = ensure(state.candidatesPath, "candidatesPath");
60+
const validatedPath = ensure(state.validatedPath, "validatedPath");
61+
const diffPath = ensure(state.diffPath, "diffPath");
62+
const commentsPath = ensure(state.commentsPath, "commentsPath");
63+
const descriptionPath = ensure(state.descriptionPath, "descriptionPath");
64+
const headSha = ensure(state.headSha, "headSha");
65+
66+
const promptCtx: GitlabReviewPromptContext = {
67+
projectPath: state.projectPath,
68+
mrIid,
69+
mrTitle: state.mrTitle ?? "",
70+
sourceBranch: state.sourceBranch ?? "",
71+
targetBranch: state.targetBranch ?? "",
72+
headSha,
73+
diffPath,
74+
commentsPath,
75+
descriptionPath,
76+
candidatesPath,
77+
validatedPath,
78+
includeSuggestions: state.includeSuggestions,
79+
securityReviewEnabled: state.securityReviewEnabled,
80+
};
81+
82+
const prompt = generateGitlabReviewValidatorPrompt(promptCtx);
83+
await fs.mkdir(path.dirname(promptPath), { recursive: true });
84+
await fs.writeFile(promptPath, prompt);
85+
console.log(
86+
`Wrote Pass-2 validator prompt (${prompt.length} bytes) to ${promptPath} (overwrote Pass 1)`,
87+
);
88+
}
89+
90+
if (import.meta.main) {
91+
run().catch((error) => {
92+
console.error("gitlab-prepare-validator failed:", error);
93+
process.exit(1);
94+
});
95+
}
96+
97+
export { run };

0 commit comments

Comments
 (0)