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):
- 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.
- 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.
.src.sh source files bundled via make script-build. The .sh files are generated — # GENERATED from <name>.src.sh -- DO NOT EDIT.
- Harness:
forge.github and forge.gitlab sections with per-forge policy, skills, host_files, env (runner + sandbox). Top-level retains forge-neutral fields only.
- Env normalization: Harness
env sections map forge-specific input vars to uniform names (PR_URL, FULLSEND_FORGE).
- Policy split: GitHub allows
gh not curl; GitLab allows curl not gh. Both allow node.
- 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.
- GitLab
_gitlab_api() helper: Each agent's GitLab ops lib defines a private _gitlab_api() curl wrapper with timeouts, PRIVATE-TOKEN header, error handling.
- 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_token — echo "::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_label — POST /projects/:id/labels (idempotent via || true)
forge_add_pr_label — PUT /projects/:id/merge_requests/:iid with add_labels
forge_post_pr_comment — POST /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
- Create lib files:
fix-ops.lib.sh, github-fix-ops.lib.sh, gitlab-fix-ops.lib.sh
- Create
.src.sh source files from current scripts, source the ops lib, replace gh calls with forge_*
- Create
policies/gitlab/fix.yaml (based on policies/gitlab/code.yaml)
- Create
env/github/fix.env, env/gitlab/fix.env
- Split
skills/fix-review/ into shared + forge subdirs
- Update
harness/fix.yaml with forge sections
- Update
agents/fix.md, process-fix-result.py
- Update
Makefile with new BUNDLE_SRCS
- Run
make script-build to generate bundled .sh files
- Update
scripts/post-fix-test.sh with GitLab tests
- Run
make script-test to verify all tests pass
Verification
bash scripts/post-fix-test.sh — all existing GitHub tests pass
- New GitLab test cases pass (mock
curl, FULLSEND_FORGE=gitlab)
make script-build succeeds (bundled .sh files generated)
make script-test passes
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, andbranch-guard.lib.sh. The post-failure-report lib already has partial forge awareness (checks forforge_post_pr_comment, handlesGITLAB_TOKENin 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'scode-ops.lib.shprovides.The upstream runtime (
fullsend-ai/fullsend) already supportsResolveForge(platform)which mergesforge.<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_FORGEenv var.Patterns to Follow
Established by triage (merged), code (merged), review (#815), and retro (#817):
{agent}-ops.lib.sh(dispatcher) +github-{agent}-ops.lib.sh+gitlab-{agent}-ops.lib.sh. All forge functions prefixedforge_*. Dispatcher uses include guard +caseonFULLSEND_FORGE.forge_*functions. Noif github/if gitlabin the main script — the dispatch is inside the ops lib..src.shsource files bundled viamake script-build. The.shfiles are generated —# GENERATED from <name>.src.sh -- DO NOT EDIT.forge.githubandforge.gitlabsections with per-forgepolicy,skills,host_files,env(runner + sandbox). Top-level retains forge-neutral fields only.envsections map forge-specific input vars to uniform names (PR_URL,FULLSEND_FORGE).ghnotcurl; GitLab allowscurlnotgh. Both allownode.skills/{skill}/github/SKILL.md,skills/{skill}/gitlab/SKILL.md). Shared methodology stays at top level._gitlab_api()helper: Each agent's GitLab ops lib defines a private_gitlab_api()curl wrapper with timeouts,PRIVATE-TOKENheader, error handling.gitlab.comandgitlab.cee.redhat.com.GitHub-specific inventory
scripts/post-fix.src.sh (~495 lines, ~4
ghcalls + 1 hardcodedgithub.comURL)gh pr view "${PR_NUMBER}"curlGitLab MR APIhttps://x-access-token:${PUSH_TOKEN}@github.com/...https://oauth2:${PUSH_TOKEN}@${GITLAB_HOST}/...export GH_TOKEN="${PUSH_TOKEN}"export GITLAB_TOKEN="${PUSH_TOKEN}"gh label create "needs-human"curlPOST/projects/:id/labelsgh pr edit "${PR_NUMBER}" --add-label "needs-human"curlPUT/projects/:id/merge_requests/:iidwithadd_labelsDoes NOT source
code-ops.lib.sh— sources onlypost-failure-report.lib.sh,gitleaks-install.lib.sh,branch-guard.lib.sh.scripts/pre-fix.sh (~169 lines, ~0
ghcalls, mostly forge-agnostic)Only GitHub-specific references are CI env vars (
GITHUB_WORKSPACE,GITHUB_PATH), not forge API calls. NeedsCI_PROJECT_DIRfallback for GitLab CI.scripts/process-fix-result.py (~212 lines, 1
ghcall)subprocess.run(["gh", "pr", "comment", ...])curlPOST/projects/:id/merge_requests/:iid/notesagents/fix.md
gh pr review --bodyTRIGGER_SOURCE[bot]suffix bot detection.github/skills/fix-review/SKILL.md (6
ghreferences)Uses
gh pr view,gh pr difffor PR data fetching. Needs forge split intoskills/fix-review/github/SKILL.mdandskills/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 onFULLSEND_FORGEsourcing the correct forge ops lib. Copy the pattern fromscripts/lib/code-ops.lib.sh.2.
scripts/lib/github-fix-ops.lib.sh— GitHub forge opsExtract from current
post-fix.src.sh. Functions needed:forge_validate_pr_url— regex validateshttps://github.com/.../pull/Nforge_parse_pr_url— setsREPO_FULL_NAME,PR_NUMBERforge_get_pr_head_ref "$pr_number"—gh pr viewto get head refforge_set_push_remote "$repo" "$token"— setsx-access-token:${token}@github.com/${repo}.gitforge_create_label "$repo" "$name" "$description" "$color"—gh label createforge_add_pr_label "$repo" "$pr_number" "$label"—gh pr edit --add-labelforge_post_pr_comment "$repo" "$pr_number" "$body"—gh pr commentforge_mask_token—echo "::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 opsSame interface using
curl+ GitLab REST API:_gitlab_api()— private curl wrapper withPRIVATE-TOKEN: ${GITLAB_TOKEN}, timeouts, error handlingforge_validate_pr_url— validates GitLab MR URL pattern + allowed hosts (gitlab.com,gitlab.cee.redhat.com)forge_parse_pr_url— extractsGITLAB_HOST,REPO_FULL_NAME,REPO_ENCODED,PR_NUMBERfrom GitLab URL (handles/-/merge_requests/N)forge_get_pr_head_ref "$pr_number"—curlGET/projects/:id/merge_requests/:iidto getsource_branchforge_set_push_remote "$repo" "$token"— setsoauth2:${token}@${GITLAB_HOST}/${repo}.gitforge_create_label—POST /projects/:id/labels(idempotent via|| true)forge_add_pr_label—PUT /projects/:id/merge_requests/:iidwithadd_labelsforge_post_pr_comment—POST /projects/:id/merge_requests/:iid/notesforge_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-fixConvert current
pre-fix.shto.src.sh. Changes:FULLSEND_FORGEto required env varsfix-ops.lib.shGITHUB_WORKSPACEwithforge_get_workspace_dirGITHUB_PATHwith conditional (${GITHUB_PATH:-${CI_ENV:-/dev/null}})5.
policies/gitlab/fix.yamlBased on
policies/gitlab/code.yamlpattern. Monolithic policy with:vertex_ai:api.anthropic.com,*.googleapis.com; binaries:claude,nodegitlab_api:gitlab.com,gitlab.cee.redhat.com; binaries:curl,git,node,pre-commit(NOgh)gitleaks_releases: same as codepackage_registries: same as code6.
env/github/fix.env(~5 lines)7.
env/gitlab/fix.env(~5 lines)8.
skills/fix-review/github/SKILL.md— GitHub CLI recipesMove current
gh pr view,gh pr diffcommands fromskills/fix-review/SKILL.mdinto this forge-specific file.9.
skills/fix-review/gitlab/SKILL.md— GitLab CLI recipesGitLab equivalents using
curl:curlGET/projects/:id/merge_requests/:iidfor MR metadatacurlGET/projects/:id/merge_requests/:iid/changesfor MR diffGITLAB_HOST,REPO_ENCODEDfromREPO_FULL_NAMEFiles to Modify
harness/fix.yamlFULLSEND_FORGE: githubto existingforge.github.env.runnerandforge.github.env.sandboxskills/github-forgeandskills/fix-review/githubtoforge.github.skillshost_filesentry forenv/github/fix.envtoforge.githubforge.gitlabblock with:policy: policies/gitlab/fix.yamlpre_script: scripts/pre-fix.sh,post_script: scripts/post-fix.shskills: [skills/gitlab-forge, skills/fix-review/gitlab]host_fileswithenv/gitlab/fix.envenv.runner:PUSH_TOKEN,PUSH_TOKEN_SOURCE,REPO_FULL_NAME,PR_NUMBER=${MR_NUMBER},GITLAB_TOKEN,FULLSEND_FORGE: gitlabenv.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/EMAILscripts/post-fix.src.shfix-ops.lib.sh(add alongside existing lib sources)gh pr view(line 120) withforge_get_pr_head_refgithub.compush URL (line 354) withforge_set_push_remoteexport GH_TOKEN="${PUSH_TOKEN}"(line 387) withforge_setup_push_tokengh label create(line 475) withforge_create_labelgh pr edit --add-label(line 478) withforge_add_pr_labelscripts/process-fix-result.py--forgeargument (or readFULLSEND_FORGEenv var)gh pr comment(line 130) with forge-aware dispatch:gh pr commentfor GitHub,curlPOST for GitLabagents/fix.md[bot]suffix detection to also handle GitLab bot patterns.gitlab-ci.ymlalongside.github/in protected pathsFULLSEND_FORGEas an inputcurlto tools listskills/fix-review/SKILL.mdghcommands (moved toskills/fix-review/github/SKILL.md)Makefilescripts/pre-fix.src.shandscripts/post-fix.src.shtoBUNDLE_SRCSscripts/post-fix-test.shFULLSEND_FORGE=githubcurl, setFULLSEND_FORGE=gitlab,GITLAB_TOKEN, GitLab MR URL, verify noghcallsImplementation Order
fix-ops.lib.sh,github-fix-ops.lib.sh,gitlab-fix-ops.lib.sh.src.shsource files from current scripts, source the ops lib, replaceghcalls withforge_*policies/gitlab/fix.yaml(based onpolicies/gitlab/code.yaml)env/github/fix.env,env/gitlab/fix.envskills/fix-review/into shared + forge subdirsharness/fix.yamlwith forge sectionsagents/fix.md,process-fix-result.pyMakefilewith newBUNDLE_SRCSmake script-buildto generate bundled.shfilesscripts/post-fix-test.shwith GitLab testsmake script-testto verify all tests passVerification
bash scripts/post-fix-test.sh— all existing GitHub tests passcurl,FULLSEND_FORGE=gitlab)make script-buildsucceeds (bundled.shfiles generated)make script-testpasses