Skip to content

Make prioritize agent multi-forge (GitHub + GitLab) #818

Description

@ggallen

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_URLISSUE_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.shgithub-<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 issuedescription: Score an issue
  • tools: Bash(gh,jq)tools: Bash(gh,curl,jq)
  • "evaluate a single GitHub issue" → "evaluate a single issue"
  • GITHUB_ISSUE_URLISSUE_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.yamlpolicies/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_commentcurl -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

  1. Run make script-build to regenerate bundled .sh files
  2. Run scripts/post-prioritize-test.sh — all existing GitHub tests should pass with FULLSEND_FORGE=github
  3. Run new GitLab test cases
  4. Validate harness YAML with fullsend validate harness/prioritize.yaml (if available)
  5. Manual smoke test: trigger prioritize on a GitLab issue and verify comment is posted

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Status
    In progress

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions