Skip to content

Make fix agent multi-forge (GitHub + GitLab) #860

Description

@ggallen

Context

The fix agent (Phase 4 in the multi-forge work breakdown) needs GitLab support. The triage agent (Phase 1) is already multi-forge on main. The code agent (PR #813) is merged to main. The review agent (PR #815) and retro agent (PR #817) are in-progress.

The fix agent shares infrastructure with the code agent — it uses post-failure-report.lib.sh, gitleaks-install.lib.sh, and branch-guard.lib.sh. The post-failure-report lib already has partial forge awareness (checks for forge_post_pr_comment, handles GITLAB_TOKEN in redaction). The fix agent's post-script operations (verify PR head ref, push branch, post PR comment, create/add labels) are a subset of what the code agent's code-ops.lib.sh provides.

The upstream runtime (fullsend-ai/fullsend) already supports ResolveForge(platform) which merges forge.<platform> sections into top-level harness fields. No runtime changes are needed.

Goal: The fix agent harness runs on both GitHub and GitLab, dispatching forge-specific operations via FULLSEND_FORGE env var.

Patterns to Follow

Established by triage (merged), code (merged), review (#815), and retro (#817):

  1. Three-file lib pattern: {agent}-ops.lib.sh (dispatcher) + github-{agent}-ops.lib.sh + gitlab-{agent}-ops.lib.sh. All forge functions prefixed forge_*. Dispatcher uses include guard + case on FULLSEND_FORGE.
  2. Same pre/post scripts for both forges. Scripts source the dispatcher lib and call forge_* functions. No if github/if gitlab in the main script — the dispatch is inside the ops lib.
  3. .src.sh source files bundled via make script-build. The .sh files are generated — # GENERATED from <name>.src.sh -- DO NOT EDIT.
  4. Harness: forge.github and forge.gitlab sections with per-forge policy, skills, host_files, env (runner + sandbox). Top-level retains forge-neutral fields only.
  5. Env normalization: Harness env sections map forge-specific input vars to uniform names (PR_URL, FULLSEND_FORGE).
  6. Policy split: GitHub allows gh not curl; GitLab allows curl not gh. Both allow node.
  7. Skills split: Agent-specific skills with forge-specific CLI recipes in subdirs (skills/{skill}/github/SKILL.md, skills/{skill}/gitlab/SKILL.md). Shared methodology stays at top level.
  8. GitLab _gitlab_api() helper: Each agent's GitLab ops lib defines a private _gitlab_api() curl wrapper with timeouts, PRIVATE-TOKEN header, error handling.
  9. GitLab host validation: Allowed hosts are gitlab.com and gitlab.cee.redhat.com.

GitHub-specific inventory

scripts/post-fix.src.sh (~495 lines, ~4 gh calls + 1 hardcoded github.com URL)

Line Call Purpose GitLab equivalent
120 gh pr view "${PR_NUMBER}" Verify PR head ref (branch validation) curl GitLab MR API
354 https://x-access-token:${PUSH_TOKEN}@github.com/... Push URL with auth https://oauth2:${PUSH_TOKEN}@${GITLAB_HOST}/...
387 export GH_TOKEN="${PUSH_TOKEN}" Token setup for gh calls export GITLAB_TOKEN="${PUSH_TOKEN}"
475 gh label create "needs-human" Create label curl POST /projects/:id/labels
478 gh pr edit "${PR_NUMBER}" --add-label "needs-human" Add label to PR curl PUT /projects/:id/merge_requests/:iid with add_labels

Does NOT source code-ops.lib.sh — sources only post-failure-report.lib.sh, gitleaks-install.lib.sh, branch-guard.lib.sh.

scripts/pre-fix.sh (~169 lines, ~0 gh calls, mostly forge-agnostic)

Only GitHub-specific references are CI env vars (GITHUB_WORKSPACE, GITHUB_PATH), not forge API calls. Needs CI_PROJECT_DIR fallback for GitLab CI.

scripts/process-fix-result.py (~212 lines, 1 gh call)

Line Call Purpose GitLab equivalent
130 subprocess.run(["gh", "pr", "comment", ...]) Post PR summary comment curl POST /projects/:id/merge_requests/:iid/notes

agents/fix.md

  • Line 47: References gh pr review --body
  • Line 56: "GitHub username" for TRIGGER_SOURCE
  • Line 64: GitHub-specific [bot] suffix bot detection
  • Line 88: Protected paths include .github/

skills/fix-review/SKILL.md (6 gh references)

Uses gh pr view, gh pr diff for PR data fetching. Needs forge split into skills/fix-review/github/SKILL.md and skills/fix-review/gitlab/SKILL.md.

Files to Create

1. scripts/lib/fix-ops.lib.sh — Forge dispatcher (~25 lines)

Include guard (FIX_OPS_SH_LOADED), case switch on FULLSEND_FORGE sourcing the correct forge ops lib. Copy the pattern from scripts/lib/code-ops.lib.sh.

2. scripts/lib/github-fix-ops.lib.sh — GitHub forge ops

Extract from current post-fix.src.sh. Functions needed:

  • forge_validate_pr_url — regex validates https://github.com/.../pull/N
  • forge_parse_pr_url — sets REPO_FULL_NAME, PR_NUMBER
  • forge_get_pr_head_ref "$pr_number"gh pr view to get head ref
  • forge_set_push_remote "$repo" "$token" — sets x-access-token:${token}@github.com/${repo}.git
  • forge_create_label "$repo" "$name" "$description" "$color"gh label create
  • forge_add_pr_label "$repo" "$pr_number" "$label"gh pr edit --add-label
  • forge_post_pr_comment "$repo" "$pr_number" "$body"gh pr comment
  • forge_mask_tokenecho "::add-mask::${GH_TOKEN}"
  • forge_setup_push_token "$token"export GH_TOKEN="${token}"
  • forge_get_workspace_dir${GITHUB_WORKSPACE:-/tmp}

3. scripts/lib/gitlab-fix-ops.lib.sh — GitLab forge ops

Same interface using curl + GitLab REST API:

  • _gitlab_api() — private curl wrapper with PRIVATE-TOKEN: ${GITLAB_TOKEN}, timeouts, error handling
  • forge_validate_pr_url — validates GitLab MR URL pattern + allowed hosts (gitlab.com, gitlab.cee.redhat.com)
  • forge_parse_pr_url — extracts GITLAB_HOST, REPO_FULL_NAME, REPO_ENCODED, PR_NUMBER from GitLab URL (handles /-/merge_requests/N)
  • forge_get_pr_head_ref "$pr_number"curl GET /projects/:id/merge_requests/:iid to get source_branch
  • forge_set_push_remote "$repo" "$token" — sets oauth2:${token}@${GITLAB_HOST}/${repo}.git
  • forge_create_labelPOST /projects/:id/labels (idempotent via || true)
  • forge_add_pr_labelPUT /projects/:id/merge_requests/:iid with add_labels
  • forge_post_pr_commentPOST /projects/:id/merge_requests/:iid/notes
  • forge_mask_token — no-op (GHA-only, harmless on GitLab CI)
  • forge_setup_push_token "$token"export GITLAB_TOKEN="${token}"
  • forge_get_workspace_dir${CI_PROJECT_DIR:-/tmp}

4. scripts/pre-fix.src.sh — Source file for pre-fix

Convert current pre-fix.sh to .src.sh. Changes:

  • Add FULLSEND_FORGE to required env vars
  • Source fix-ops.lib.sh
  • Replace GITHUB_WORKSPACE with forge_get_workspace_dir
  • Replace GITHUB_PATH with conditional (${GITHUB_PATH:-${CI_ENV:-/dev/null}})
  • Keep all business logic unchanged (input validation, sandbox prep)

5. policies/gitlab/fix.yaml

Based on policies/gitlab/code.yaml pattern. Monolithic policy with:

  • vertex_ai: api.anthropic.com, *.googleapis.com; binaries: claude, node
  • gitlab_api: gitlab.com, gitlab.cee.redhat.com; binaries: curl, git, node, pre-commit (NO gh)
  • gitleaks_releases: same as code
  • package_registries: same as code

6. env/github/fix.env (~5 lines)

export PR_URL="${GITHUB_PR_URL}"
export GH_TOKEN="${GH_TOKEN}"
export FULLSEND_FORGE="github"

7. env/gitlab/fix.env (~5 lines)

export PR_URL="${GITLAB_MR_URL}"
export GITLAB_TOKEN="${GITLAB_TOKEN}"
export FULLSEND_FORGE="gitlab"

8. skills/fix-review/github/SKILL.md — GitHub CLI recipes

Move current gh pr view, gh pr diff commands from skills/fix-review/SKILL.md into this forge-specific file.

9. skills/fix-review/gitlab/SKILL.md — GitLab CLI recipes

GitLab equivalents using curl:

  • curl GET /projects/:id/merge_requests/:iid for MR metadata
  • curl GET /projects/:id/merge_requests/:iid/changes for MR diff
  • Environment setup: derive GITLAB_HOST, REPO_ENCODED from REPO_FULL_NAME

Files to Modify

harness/fix.yaml

  • Add FULLSEND_FORGE: github to existing forge.github.env.runner and forge.github.env.sandbox
  • Add skills/github-forge and skills/fix-review/github to forge.github.skills
  • Add host_files entry for env/github/fix.env to forge.github
  • Add complete forge.gitlab block with:
    • policy: policies/gitlab/fix.yaml
    • pre_script: scripts/pre-fix.sh, post_script: scripts/post-fix.sh
    • skills: [skills/gitlab-forge, skills/fix-review/gitlab]
    • host_files with env/gitlab/fix.env
    • env.runner: PUSH_TOKEN, PUSH_TOKEN_SOURCE, REPO_FULL_NAME, PR_NUMBER=${MR_NUMBER}, GITLAB_TOKEN, FULLSEND_FORGE: gitlab
    • env.sandbox: PR_NUMBER=${MR_NUMBER}, REPO_FULL_NAME, TRIGGER_SOURCE, HUMAN_INSTRUCTION, FIX_ITERATION, GITLAB_TOKEN, FULLSEND_FORGE: gitlab, GIT_AUTHOR_NAME/EMAIL, GIT_COMMITTER_NAME/EMAIL

scripts/post-fix.src.sh

  • Source fix-ops.lib.sh (add alongside existing lib sources)
  • Replace gh pr view (line 120) with forge_get_pr_head_ref
  • Replace hardcoded github.com push URL (line 354) with forge_set_push_remote
  • Replace export GH_TOKEN="${PUSH_TOKEN}" (line 387) with forge_setup_push_token
  • Replace gh label create (line 475) with forge_create_label
  • Replace gh pr edit --add-label (line 478) with forge_add_pr_label

scripts/process-fix-result.py

  • Accept --forge argument (or read FULLSEND_FORGE env var)
  • Replace hardcoded gh pr comment (line 130) with forge-aware dispatch: gh pr comment for GitHub, curl POST for GitLab

agents/fix.md

  • Line 47: "gh pr review --body" → forge-neutral description referencing forge skill
  • Line 56: "GitHub username" → "forge username"
  • Line 64: Generalize [bot] suffix detection to also handle GitLab bot patterns
  • Line 88: Add .gitlab-ci.yml alongside .github/ in protected paths
  • Add FULLSEND_FORGE as an input
  • Add curl to tools list

skills/fix-review/SKILL.md

  • Remove forge-specific gh commands (moved to skills/fix-review/github/SKILL.md)
  • Keep shared methodology (reading review body, understanding findings, fix approach)
  • Add reference to forge-specific skill for CLI commands

Makefile

  • Add scripts/pre-fix.src.sh and scripts/post-fix.src.sh to BUNDLE_SRCS

scripts/post-fix-test.sh

  • Update existing GitHub tests to set FULLSEND_FORGE=github
  • Add GitLab test section: mock curl, set FULLSEND_FORGE=gitlab, GITLAB_TOKEN, GitLab MR URL, verify no gh calls

Implementation Order

  1. Create lib files: fix-ops.lib.sh, github-fix-ops.lib.sh, gitlab-fix-ops.lib.sh
  2. Create .src.sh source files from current scripts, source the ops lib, replace gh calls with forge_*
  3. Create policies/gitlab/fix.yaml (based on policies/gitlab/code.yaml)
  4. Create env/github/fix.env, env/gitlab/fix.env
  5. Split skills/fix-review/ into shared + forge subdirs
  6. Update harness/fix.yaml with forge sections
  7. Update agents/fix.md, process-fix-result.py
  8. Update Makefile with new BUNDLE_SRCS
  9. Run make script-build to generate bundled .sh files
  10. Update scripts/post-fix-test.sh with GitLab tests
  11. Run make script-test to verify all tests pass

Verification

  1. bash scripts/post-fix-test.sh — all existing GitHub tests pass
  2. New GitLab test cases pass (mock curl, FULLSEND_FORGE=gitlab)
  3. make script-build succeeds (bundled .sh files generated)
  4. make script-test passes

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Status
    Todo

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions