Context
The fullsend platform is converting all agents from GitHub-only to multi-forge (GitHub + GitLab) support. The conversion follows a consistent pattern established by triage (merged), code, review, and retro (all in PR). Prioritize is Phase 6 in the rollout.
The plan on branch worktree-multi-forge-agents-plan in the fullsend repo (docs/plans/multi-forge-agents-work-breakdown.md) tracks the overall effort.
Decision: GitLab score storage — The forge_update_project_scores function on GitLab will attempt to write scores via the GitLab custom fields API (available on Premium/Ultimate). If the API returns 403/404 (feature unavailable on Free tier), it logs a notice and returns success — graceful degradation, not failure. The reasoning comment (which already contains the full score breakdown) is always posted regardless of tier, so no information is lost.
The Established Multi-Forge Pattern
Every converted agent follows the same 6-layer pattern. Prioritize must follow it exactly.
- Harness YAML — Remove top-level
policy:. Add parallel forge.github / forge.gitlab blocks with: policy, skills, host_files, env (including FULLSEND_FORGE).
- Agent prompt — Forge-neutral.
GITHUB_ISSUE_URL → ISSUE_URL. Replace gh CLI references with "use your forge skill". Add curl to tools.
- Env files — Split into
env/github/<agent>.env and env/gitlab/<agent>.env. Map forge-specific vars to neutral names.
- Scripts — Shared pre/post scripts for both forges. Dispatch internally via ops library (
<agent>-ops.lib.sh → github-<agent>-ops.lib.sh / gitlab-<agent>-ops.lib.sh). Both implement identical forge_*() function signatures.
- Policies — Split into
policies/github/<agent>.yaml (allows gh binary) and policies/gitlab/<agent>.yaml (allows curl binary).
- Skills — Inject
skills/github-forge or skills/gitlab-forge per forge.
Key: both forges share the same script files — dispatch happens inside via the ops library, NOT via separate script files per forge.
Changes
1. Agent prompt — agents/prioritize.md
description: Score a GitHub issue → description: Score an issue
tools: Bash(gh,jq) → tools: Bash(gh,curl,jq)
- "evaluate a single GitHub issue" → "evaluate a single issue"
GITHUB_ISSUE_URL → ISSUE_URL, add examples for both forges
- Replace hardcoded
gh issue view "$GITHUB_ISSUE_URL" --json ... → "Use the forge-appropriate command from your forge skill to fetch the issue"
- "A post-script handles all GitHub mutations" → "A post-script handles all forge mutations"
- Remove all other "GitHub" references in prose
2. Harness — harness/prioritize.yaml
- Remove top-level
policy: policies/prioritize.yaml
- Update
forge.github block to add:
policy: policies/github/prioritize.yaml
skills: [skills/github-forge]
host_files: entry for env/github/prioritize.env
- Normalize env:
ISSUE_URL: ${GITHUB_ISSUE_URL}, add FULLSEND_FORGE: github
- Keep
ORG and PROJECT_NUMBER (GitHub-only, for Projects V2)
- Add
forge.gitlab block with:
policy: policies/gitlab/prioritize.yaml
pre_script: scripts/pre-prioritize.sh
post_script: scripts/post-prioritize.sh
skills: [skills/gitlab-forge]
host_files: entry for env/gitlab/prioritize.env
env.runner: — ISSUE_URL: ${GITLAB_ISSUE_URL}, GITLAB_TOKEN, GITLAB_HOST, FULLSEND_FORGE: gitlab
env.sandbox: — ISSUE_URL, GITLAB_TOKEN, GITLAB_HOST, FULLSEND_FORGE: gitlab
Use harness/triage.yaml as the structural template.
3. Env files
- Strip
env/prioritize.env of forge-specific vars (remove GITHUB_ISSUE_URL and GH_TOKEN exports; keep if any shared vars remain, delete if empty)
- Create
env/github/prioritize.env:
export ISSUE_URL="${GITHUB_ISSUE_URL}"
export GH_TOKEN="${GH_TOKEN}"
export FULLSEND_FORGE="github"
- Create
env/gitlab/prioritize.env:
export ISSUE_URL="${GITLAB_ISSUE_URL}"
export GITLAB_TOKEN="${GITLAB_TOKEN}"
export GITLAB_HOST="${GITLAB_HOST}"
export FULLSEND_FORGE="gitlab"
4. Policy split
- Move
policies/prioritize.yaml → policies/github/prioritize.yaml (contents unchanged)
- Create
policies/gitlab/prioritize.yaml — same structure but:
- Replace
github_api network policy with gitlab_api
- Endpoints:
gitlab.com and gitlab.cee.redhat.com port 443
- Binaries:
**/curl and **/node (not **/gh)
- Keep
vertex_ai policy unchanged
Use policies/gitlab/triage.yaml as the template.
5. Ops library (new files)
Follow the pattern from scripts/lib/triage-ops.lib.sh and forge-specific ops.
-
Create scripts/lib/prioritize-ops.lib.sh — dispatcher:
case "${FULLSEND_FORGE:-}" in
github) source "${SCRIPT_DIR}/lib/github-prioritize-ops.lib.sh" ;;
gitlab) source "${SCRIPT_DIR}/lib/gitlab-prioritize-ops.lib.sh" ;;
*) echo "ERROR: invalid FULLSEND_FORGE" >&2; exit 1 ;;
esac
-
Create scripts/lib/github-prioritize-ops.lib.sh — extract current GitHub logic from post-prioritize.src.sh into forge_*() functions:
forge_validate_issue_url — current GitHub URL regex
forge_parse_issue_url — extract REPO, ISSUE_NUMBER from URL
forge_update_project_scores — current Projects V2 GraphQL logic (resolve project/item IDs, update 5 custom fields)
forge_post_comment — current fullsend post-comment logic via CSMA
- Sources
github-api-csma.lib.sh internally
-
Create scripts/lib/gitlab-prioritize-ops.lib.sh — GitLab equivalents:
forge_validate_issue_url — GitLab URL regex, allowed hosts check
forge_parse_issue_url — extract GITLAB_HOST, REPO, REPO_ENCODED, ISSUE_NUMBER
forge_update_project_scores — attempt GitLab custom fields API via curl; if 403/404, log notice and return success (graceful degradation)
forge_post_comment — curl -X POST to /projects/${REPO_ENCODED}/issues/${ISSUE_NUMBER}/notes with PRIVATE-TOKEN header
- Use
_gitlab_api() helper pattern from gitlab-triage-ops.lib.sh
6. Pre-script — scripts/pre-prioritize.sh
- Source
lib/prioritize-ops.lib.sh
- Replace hardcoded
GITHUB_ISSUE_URL regex with forge_validate_issue_url "${ISSUE_URL}"
- Change notice line to use
ISSUE_URL instead of GITHUB_ISSUE_URL
- Also create
scripts/pre-prioritize.src.sh as the source file (current .sh has no .src.sh variant; the other agents use the .src.sh → bundled .sh pattern)
7. Post-script — scripts/post-prioritize.src.sh
- Source
lib/prioritize-ops.lib.sh instead of lib/github-api-csma.lib.sh
- Replace
GITHUB_ISSUE_URL with ISSUE_URL throughout
- Remove direct
gh calls — replace with:
forge_validate_issue_url "${ISSUE_URL}"
forge_parse_issue_url "${ISSUE_URL}" (sets REPO, ISSUE_NUMBER as globals)
forge_update_project_scores "${REACH}" "${IMPACT}" "${CONFIDENCE}" "${EFFORT}" "${SCORE}"
forge_post_comment "${COMMENT}"
- Remove direct references to
GH_TOKEN, ORG, PROJECT_NUMBER from the script body — those are consumed inside the GitHub ops lib
- Keep the shared logic: result file discovery, JSON validation, score extraction, RICE computation, comment body construction
- Rebuild bundled
post-prioritize.sh via make script-build
8. Tests — scripts/post-prioritize-test.sh
- Update existing tests to set
FULLSEND_FORGE=github and use ISSUE_URL
- Add GitLab test section:
- Mock
curl instead of gh
- Assert no
gh calls happen on GitLab forge
- Test
forge_validate_issue_url with GitLab URLs
- Test
forge_post_comment via curl
- Test
forge_update_project_scores graceful degradation (mock 403 response)
9. Docs — docs/prioritize.md
- "Scores a GitHub issue" → "Scores an issue"
- Mention GitLab support
- Note: on GitLab Premium/Ultimate, scores are written to custom fields; on Free tier, scores are posted as comments only
Verification
- Run
make script-build to regenerate bundled .sh files
- Run
scripts/post-prioritize-test.sh — all existing GitHub tests should pass with FULLSEND_FORGE=github
- Run new GitLab test cases
- Validate harness YAML with
fullsend validate harness/prioritize.yaml (if available)
- Manual smoke test: trigger prioritize on a GitLab issue and verify comment is posted
Context
The fullsend platform is converting all agents from GitHub-only to multi-forge (GitHub + GitLab) support. The conversion follows a consistent pattern established by triage (merged), code, review, and retro (all in PR). Prioritize is Phase 6 in the rollout.
The plan on branch
worktree-multi-forge-agents-planin thefullsendrepo (docs/plans/multi-forge-agents-work-breakdown.md) tracks the overall effort.Decision: GitLab score storage — The
forge_update_project_scoresfunction on GitLab will attempt to write scores via the GitLab custom fields API (available on Premium/Ultimate). If the API returns 403/404 (feature unavailable on Free tier), it logs a notice and returns success — graceful degradation, not failure. The reasoning comment (which already contains the full score breakdown) is always posted regardless of tier, so no information is lost.The Established Multi-Forge Pattern
Every converted agent follows the same 6-layer pattern. Prioritize must follow it exactly.
policy:. Add parallelforge.github/forge.gitlabblocks with: policy, skills, host_files, env (includingFULLSEND_FORGE).GITHUB_ISSUE_URL→ISSUE_URL. ReplaceghCLI references with "use your forge skill". Addcurlto tools.env/github/<agent>.envandenv/gitlab/<agent>.env. Map forge-specific vars to neutral names.<agent>-ops.lib.sh→github-<agent>-ops.lib.sh/gitlab-<agent>-ops.lib.sh). Both implement identicalforge_*()function signatures.policies/github/<agent>.yaml(allowsghbinary) andpolicies/gitlab/<agent>.yaml(allowscurlbinary).skills/github-forgeorskills/gitlab-forgeper forge.Key: both forges share the same script files — dispatch happens inside via the ops library, NOT via separate script files per forge.
Changes
1. Agent prompt —
agents/prioritize.mddescription: Score a GitHub issue→description: Score an issuetools: Bash(gh,jq)→tools: Bash(gh,curl,jq)GITHUB_ISSUE_URL→ISSUE_URL, add examples for both forgesgh issue view "$GITHUB_ISSUE_URL" --json ...→ "Use the forge-appropriate command from your forge skill to fetch the issue"2. Harness —
harness/prioritize.yamlpolicy: policies/prioritize.yamlforge.githubblock to add:policy: policies/github/prioritize.yamlskills: [skills/github-forge]host_files:entry forenv/github/prioritize.envISSUE_URL: ${GITHUB_ISSUE_URL}, addFULLSEND_FORGE: githubORGandPROJECT_NUMBER(GitHub-only, for Projects V2)forge.gitlabblock with:policy: policies/gitlab/prioritize.yamlpre_script: scripts/pre-prioritize.shpost_script: scripts/post-prioritize.shskills: [skills/gitlab-forge]host_files:entry forenv/gitlab/prioritize.envenv.runner:—ISSUE_URL: ${GITLAB_ISSUE_URL},GITLAB_TOKEN,GITLAB_HOST,FULLSEND_FORGE: gitlabenv.sandbox:—ISSUE_URL,GITLAB_TOKEN,GITLAB_HOST,FULLSEND_FORGE: gitlabUse
harness/triage.yamlas the structural template.3. Env files
env/prioritize.envof forge-specific vars (removeGITHUB_ISSUE_URLandGH_TOKENexports; keep if any shared vars remain, delete if empty)env/github/prioritize.env:env/gitlab/prioritize.env:4. Policy split
policies/prioritize.yaml→policies/github/prioritize.yaml(contents unchanged)policies/gitlab/prioritize.yaml— same structure but:github_apinetwork policy withgitlab_apigitlab.comandgitlab.cee.redhat.comport 443**/curland**/node(not**/gh)vertex_aipolicy unchangedUse
policies/gitlab/triage.yamlas the template.5. Ops library (new files)
Follow the pattern from
scripts/lib/triage-ops.lib.shand forge-specific ops.Create
scripts/lib/prioritize-ops.lib.sh— dispatcher:Create
scripts/lib/github-prioritize-ops.lib.sh— extract current GitHub logic frompost-prioritize.src.shintoforge_*()functions:forge_validate_issue_url— current GitHub URL regexforge_parse_issue_url— extractREPO,ISSUE_NUMBERfrom URLforge_update_project_scores— current Projects V2 GraphQL logic (resolve project/item IDs, update 5 custom fields)forge_post_comment— currentfullsend post-commentlogic via CSMAgithub-api-csma.lib.shinternallyCreate
scripts/lib/gitlab-prioritize-ops.lib.sh— GitLab equivalents:forge_validate_issue_url— GitLab URL regex, allowed hosts checkforge_parse_issue_url— extractGITLAB_HOST,REPO,REPO_ENCODED,ISSUE_NUMBERforge_update_project_scores— attempt GitLab custom fields API viacurl; if 403/404, log notice and return success (graceful degradation)forge_post_comment—curl -X POSTto/projects/${REPO_ENCODED}/issues/${ISSUE_NUMBER}/noteswithPRIVATE-TOKENheader_gitlab_api()helper pattern fromgitlab-triage-ops.lib.sh6. Pre-script —
scripts/pre-prioritize.shlib/prioritize-ops.lib.shGITHUB_ISSUE_URLregex withforge_validate_issue_url "${ISSUE_URL}"ISSUE_URLinstead ofGITHUB_ISSUE_URLscripts/pre-prioritize.src.shas the source file (current.shhas no.src.shvariant; the other agents use the.src.sh→ bundled.shpattern)7. Post-script —
scripts/post-prioritize.src.shlib/prioritize-ops.lib.shinstead oflib/github-api-csma.lib.shGITHUB_ISSUE_URLwithISSUE_URLthroughoutghcalls — replace with:forge_validate_issue_url "${ISSUE_URL}"forge_parse_issue_url "${ISSUE_URL}"(setsREPO,ISSUE_NUMBERas globals)forge_update_project_scores "${REACH}" "${IMPACT}" "${CONFIDENCE}" "${EFFORT}" "${SCORE}"forge_post_comment "${COMMENT}"GH_TOKEN,ORG,PROJECT_NUMBERfrom the script body — those are consumed inside the GitHub ops libpost-prioritize.shviamake script-build8. Tests —
scripts/post-prioritize-test.shFULLSEND_FORGE=githuband useISSUE_URLcurlinstead ofghghcalls happen on GitLab forgeforge_validate_issue_urlwith GitLab URLsforge_post_commentvia curlforge_update_project_scoresgraceful degradation (mock 403 response)9. Docs —
docs/prioritize.mdVerification
make script-buildto regenerate bundled.shfilesscripts/post-prioritize-test.sh— all existing GitHub tests should pass withFULLSEND_FORGE=githubfullsend validate harness/prioritize.yaml(if available)