Skip to content

feat(gitlab): add @droid fill mode (native parity, no webhook receiver)#94

Draft
factory-nizar wants to merge 29 commits into
devfrom
feat/gitlab-fill
Draft

feat(gitlab): add @droid fill mode (native parity, no webhook receiver)#94
factory-nizar wants to merge 29 commits into
devfrom
feat/gitlab-fill

Conversation

@factory-nizar
Copy link
Copy Markdown
Contributor

@factory-nizar factory-nizar commented Jun 2, 2026

Summary

Adds GitLab support for using GitLab's **native pipeline-firing surfaces**, so no external webhook receiver is required. A new `droid-fill` CI/CD component job fires on `merge_request_event` and decides whether to fill the MR description based on the trigger phrase, a label, or an always-on input. After fill completes, the prompt strips the token from the new description so that the next merge_request_event (caused by our own update) does not re-trigger fill.

Discussion-comment triggers (`` typed as a note on the MR) are not supported here — GitLab does not fire CI on note events, so that surface would require a webhook receiver and is deliberately deferred.

Trigger Surfaces

Trigger surface How it fires Matched at
`` in MR title merge_request_event on title edit CI rules: via $CI_MERGE_REQUEST_TITLE =~ /@droid\s+fill/i
droid:fill label merge_request_event on label change CI rules: via $CI_MERGE_REQUEST_LABELS
`` in MR description merge_request_event on description edit Job runs, fetches description via API, matches in gitlab-fill-prepare.ts
automatic_fill: "true" always-on every merge_request_event CI rules: via $AUTOMATIC_FILL

Changes

File Change
gitlab/templates/fill.yml (new, +125) GitLab CI/CD component with a single droid-fill job: three rules: trigger conditions, runtime clone/install of droid-action, Droid CLI install, gitlab_mr MCP registration, state-gated droid exec, and .droid-debug/ + .droid-state.json artifact staging
src/entrypoints/gitlab-fill-prepare.ts (new, +225) Single-pass prepare entrypoint: sets up the GitLab token and Droid settings, fetches the MR (description is not exposed as an env var) via the API, runs the trigger check, and writes the fill prompt plus a FillPrepareState consumed by the CI script
src/gitlab/validation/trigger.ts (new, +92) Port of the GitHub checkContainsTrigger for fill: checkContainsFillTrigger (title/description/label/automatic), buildFillRegex, stripFillTrigger, and escapeRegExp
src/gitlab/prompts/fill.ts (new, +61) Fill prompt that writes the description back via gitlab_mr___update_mr_description (description-only; single mutation)
src/gitlab/context.ts (+9) Adds the automaticFill input and parses mr.labels from CI_MERGE_REQUEST_LABELS
test/gitlab/fill-trigger.test.ts (new, +131) Tests for trigger detection and stripFillTrigger
test/gitlab/fill-prompt.test.ts (new, +60) Tests for the fill prompt generation
test/gitlab/context.test.ts (+2) Coverage for the new context fields

Implementation Details

  • The gitlab_mr___update_mr_description MCP tool already exists in src/mcp/gitlab-mr-server.ts; no new MCP tools were added.
  • The CI script gates droid exec on "shouldRunFill": true in the state file, so jobs that fire on every MR event but don't actually need to fill exit early.
  • The fill prompt is constrained to description-only output and a single update_mr_description call, with the enabled tool set Read,Grep,Glob,LS,Execute,Skill,gitlab_mr___update_mr_description.

Testing

  • New tests added across fill-trigger.test.ts and fill-prompt.test.ts (plus context.test.ts coverage).
  • Author-reported: 469 pass total (was 445), tsc --noEmit clean, Prettier formatted.
    Manual end-to-end on a real GitLab MR for each trigger surface:
  1. Title — edit MR title to include `` → droid-fill job fires → description filled → token stripped.
  2. Label — add droid:fill label → same flow.
  3. Description — write `` in the description → API fetch matches → fill runs.
  4. Automatic-onautomatic_fill: "true" → fires on every MR open/push.

Breaking Changes

None — all changes are additive (new GitLab files and additive context fields).

Dependencies

Branches off feat/gitlab-support (PR #93). Once PR #93 merges to dev, this PR's base should be retargeted to dev.
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>

@factory-droid
Copy link
Copy Markdown
Contributor

factory-droid Bot commented Jun 2, 2026

Droid finished @factory-nizar's task —— View job


Security Review

Main correctness issue: the fill GitLab CI rules prevent the advertised description-only trigger path from ever starting when automatic_fill=false. Also, tighten stripFillTrigger/buildFillRegex to avoid Markdown mangling and common punctuation false-negatives, and reduce the fill tool allowlist to shrink prompt-injection blast radius.

@factory-droid
Copy link
Copy Markdown
Contributor

factory-droid Bot commented Jun 2, 2026

Droid finished @factory-nizar's task —— View job


@factory-nizar factory-nizar marked this pull request as draft June 2, 2026 17:41
factory-nizar and others added 21 commits June 2, 2026 10:43
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>
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>
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>
…y-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>
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>
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>
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>
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>
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>
…etry, 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>
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>
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>
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>
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>
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>
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>
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>
… 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>
Comment thread templates/fill.yml
Comment on lines +46 to +52
rules:
# Always-on mode: fire on every MR event and decide via description content.
- if: '$CI_PIPELINE_SOURCE == "merge_request_event" && $AUTOMATIC_FILL == "true"'
# Explicit trigger in MR title.
- if: '$CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_TITLE =~ /@droid\s+fill/i'
# Explicit trigger via label.
- if: '$CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_LABELS =~ /(^|,)\s*droid:fill\s*(,|$)/i'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P0] Make description-triggered fill runnable via rules

The PR description claims @droid fill in the MR description should trigger fill by fetching description via API in gitlab-fill-prepare, but the job rules: currently only allow automatic-fill, title, or label matches; a description-only trigger will never start the job when automatic_fill=false (and the title rule is also hard-coded to @droid, ignoring trigger_phrase). Since GitLab CI rules can’t inspect MR description content, the job needs to run on merge_request_event and let gitlab-fill-prepare decide whether to execute.

Suggested change
rules:
# Always-on mode: fire on every MR event and decide via description content.
- if: '$CI_PIPELINE_SOURCE == "merge_request_event" && $AUTOMATIC_FILL == "true"'
# Explicit trigger in MR title.
- if: '$CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_TITLE =~ /@droid\s+fill/i'
# Explicit trigger via label.
- if: '$CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_LABELS =~ /(^|,)\s*droid:fill\s*(,|$)/i'
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'

Comment on lines +88 to +91
return description
.replace(regex, " ")
.replace(/\s{2,}/g, " ")
.trim();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Don’t collapse MR Markdown when stripping the fill trigger

stripFillTrigger() collapses \s{2,} across the entire description after removing the token; because \s includes newlines, this can remove paragraph breaks and indentation in normal Markdown descriptions (headings/lists/code blocks).

Suggested change
return description
.replace(regex, " ")
.replace(/\s{2,}/g, " ")
.trim();
return description
.replace(regex, " ")
.replace(/[ \t]{2,}/g, " ")
.trim();

Comment on lines +35 to +38
export function buildFillRegex(triggerPhrase: string): RegExp {
const escaped = escapeRegExp(triggerPhrase);
return new RegExp(`(^|\\s)${escaped}\\s+fill([\\s.,!?;:]|$)`, "i");
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Match @droid fill when wrapped in common punctuation

buildFillRegex() currently requires whitespace or start-of-string before the trigger phrase and only allows [\s.,!?;:] after fill, so realistic Markdown like (@droid fill) or `@droid fill` won’t trigger fill even though it contains the literal phrase.

Suggested change
export function buildFillRegex(triggerPhrase: string): RegExp {
const escaped = escapeRegExp(triggerPhrase);
return new RegExp(`(^|\\s)${escaped}\\s+fill([\\s.,!?;:]|$)`, "i");
}
export function buildFillRegex(triggerPhrase: string): RegExp {
const escaped = escapeRegExp(triggerPhrase);
return new RegExp(
`(^|[\\s\\(\\[\\{\\x60])${escaped}\\s+fill([\\s\\).,!?;:\\]\\}\\x60]|$)`,
"i",
);
}

Comment thread templates/fill.yml
--env DROID_MR_IID="$DROID_MR_IID"
- |
set -o pipefail
FILL_TOOLS="Read,Grep,Glob,LS,Execute,Skill,gitlab_mr___update_mr_description"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] [security] Remove Execute (and Skill) from fill tool allowlist

Fill mode only needs repository read/search plus gitlab_mr___update_mr_description, but FILL_TOOLS currently includes Execute (and Skill), which materially increases the impact of prompt-injection from MR-controlled inputs (title/description/diff) by enabling arbitrary shell execution and easier secret exfiltration into the updated MR description and/or job artifacts.

Suggested change
FILL_TOOLS="Read,Grep,Glob,LS,Execute,Skill,gitlab_mr___update_mr_description"
FILL_TOOLS="Read,Grep,Glob,LS,gitlab_mr___update_mr_description"

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>
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>
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>
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
…le 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>
…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>
factory-nizar and others added 2 commits June 2, 2026 16:12
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>
Adds GitLab support for `@droid fill` via the three native pipeline-
firing surfaces that don't require a webhook receiver: MR description,
MR title, and labels. Plus an `automatic_fill` always-on mode.

How the trigger works:

  * `automatic_fill: "true"` -> droid-fill runs on every MR event
    and decides to fill based on description content.
  * `@droid fill` in MR title -> matched at rule level via
    $CI_MERGE_REQUEST_TITLE.
  * `droid:fill` label -> matched at rule level via
    $CI_MERGE_REQUEST_LABELS.
  * `@droid fill` in MR description -> not matchable at rule level
    (description isn't in env), so the job runs and exits early via
    state file if no match found in title/labels and the description
    fetched from the API doesn't contain the phrase.

After fill completes the prompt instructs the model to strip the
`@droid fill` token from the new description so the next
`merge_request_event` (fired by our own update) does not re-fire fill.

Discussion-comment triggers (`@droid fill` posted as a note on the
MR) still require a webhook receiver because GitLab does not fire CI
on note events. That subset is deliberately deferred.

Files:

  * `gitlab/templates/fill.yml` — new GitLab CI/CD Component with
    one `droid-fill` job, three trigger rules, MCP registration,
    `.droid-debug/` artifact staging.
  * `src/gitlab/validation/trigger.ts` — port of GitHub's
    `checkContainsTrigger` for the fill path with
    `checkContainsFillTrigger` + `stripFillTrigger`.
  * `src/gitlab/prompts/fill.ts` — fill prompt mirroring the GitHub
    `fill-prompt.ts` but writing back via
    `gitlab_mr___update_mr_description` instead of
    `github_pr___update_pr_description`.
  * `src/entrypoints/gitlab-fill-prepare.ts` — single-pass prepare
    that reads the MR description via API, checks the trigger, and
    writes the fill prompt + state file.
  * `src/gitlab/context.ts` — adds `automaticFill` input and
    `mr.labels` parsed from `CI_MERGE_REQUEST_LABELS`.

Tests: 24 new tests across `fill-trigger.test.ts` and
`fill-prompt.test.ts`. 469 pass, typecheck clean.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Base automatically changed from feat/gitlab-support to dev June 3, 2026 17:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant