Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ Ask yourself:
that logic lives.
- [ ] **Both:** Some features may require changes in both.
- [ ] **Generated scripts:** `scripts/pre-code.sh`, `scripts/post-code.sh`,
`scripts/post-fix.sh`, `scripts/pre-prioritize.sh`, `scripts/post-prioritize.sh`,
`scripts/pre-fix.sh`, `scripts/post-fix.sh`, `scripts/pre-prioritize.sh`, `scripts/post-prioritize.sh`,
`scripts/pre-retro.sh`, `scripts/post-retro.sh`,
`scripts/pre-review.sh`, `scripts/post-review.sh`,
`scripts/pre-triage.sh`, and `scripts/post-triage.sh` are generated
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
.DEFAULT_GOAL := help
.PHONY: help script-build check-bundle script-test test

BUNDLE_SRCS := scripts/pre-code.src.sh scripts/post-code.src.sh scripts/post-fix.src.sh scripts/pre-prioritize.src.sh scripts/post-prioritize.src.sh scripts/pre-retro.src.sh scripts/post-retro.src.sh scripts/pre-review.src.sh scripts/post-review.src.sh scripts/pre-triage.src.sh scripts/post-triage.src.sh
BUNDLE_SRCS := scripts/pre-code.src.sh scripts/post-code.src.sh scripts/pre-fix.src.sh scripts/post-fix.src.sh scripts/pre-prioritize.src.sh scripts/post-prioritize.src.sh scripts/pre-retro.src.sh scripts/post-retro.src.sh scripts/pre-review.src.sh scripts/post-review.src.sh scripts/pre-triage.src.sh scripts/post-triage.src.sh
BUNDLE_OUTS := $(BUNDLE_SRCS:.src.sh=.sh)
LIB_DEPS := $(wildcard scripts/lib/*.lib.sh)

Expand Down
30 changes: 19 additions & 11 deletions agents/fix.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,25 +44,32 @@ each other or miss the reviewer's actual intent.
You operate in one of two modes depending on how you were triggered:

- **Bot-triggered** (review agent requested changes): The review agent posts
all findings as a single review body (via `gh pr review --body`). Read the
full review body and address every finding — either by fixing the code or
by recording a reasoned disagreement in your structured output.
all findings as a single review body. Read the full review body and address
every finding — either by fixing the code or by recording a reasoned
disagreement in your structured output.

- **Human-triggered** (`/fs-fix [instruction]`): Follow the human's instruction.
The instruction takes precedence over any prior bot review feedback. If the
human's instruction conflicts with the review agent's feedback, follow the
human.

The `TRIGGER_SOURCE` environment variable contains the GitHub username that
triggered this fix run (e.g., `"orgname-review[bot]"` for bot-triggered,
`"alice"` for human-triggered). Usernames ending in `[bot]` indicate bot
triggers. When triggered by a human (username doesn't end in `[bot]`), the
`HUMAN_INSTRUCTION` environment variable contains the instruction text.
The `TRIGGER_SOURCE` environment variable contains the forge username that
triggered this fix run (e.g., `"orgname-review[bot]"` on GitHub,
`"project_123_bot"` on GitLab, or `"alice"` for human-triggered).
Usernames ending in `[bot]` (GitHub) or `_bot` (GitLab) indicate bot
triggers. When triggered by a human, the `HUMAN_INSTRUCTION` environment
variable contains the instruction text.

**Important:** `TRIGGER_SOURCE` is a GitHub username — not the value you
**Important:** `TRIGGER_SOURCE` is a forge username — not the value you
write to `agent-result.json`. The `trigger_source` field in structured output
must be normalized to `"bot"` or `"human"` (the schema enum). Map it:
if the username ends in `[bot]`, use `"bot"`; otherwise use `"human"`.
must be normalized to `"bot"` or `"human"` (the schema enum). Map it
using the forge-specific convention: on GitHub (`FULLSEND_FORGE=github`),
usernames ending in `[bot]` are bots; on GitLab (`FULLSEND_FORGE=gitlab`),
usernames ending in `_bot` are bots. All other usernames are `"human"`.

The `FULLSEND_FORGE` environment variable indicates which forge platform is
in use (`"github"` or `"gitlab"`). Use forge-specific CLI commands from your
forge skill accordingly.

## Zero-trust principle

Expand All @@ -86,6 +93,7 @@ merge conflicts, linter suggestions, or other incidental context:
- `.cursor/` — editor agent configuration
- `.gitattributes`
- `.github/` — CI and GitHub configuration
- `.gitlab-ci.yml` — GitLab CI configuration
- `.pre-commit-config.yaml`
- `AGENTS.md`
- `agents/` — agent definitions
Expand Down
41 changes: 33 additions & 8 deletions docs/fix.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,9 @@ See [Customizing with AGENTS.md](https://fullsend.sh/docs/guides/user/customizin

### Variables

None.
| Variable | Default | Effect |
|----------|---------|--------|
| `FULLSEND_FORGE` | `github` | Selects the forge platform (`github` or `gitlab`). Set automatically by the harness `forge` block. |

## How the agent works

Expand All @@ -75,7 +77,7 @@ The fix agent follows a similar pipeline to the [code agent](code.md), with an a
| Input | Source | How it gets there |
|-------|--------|-------------------|
| Review body | Latest `CHANGES_REQUESTED` review from the review bot | Pre-fetched on the runner before the sandbox starts, injected as `review-body.txt` |
| PR diff | `gh pr diff` inside the sandbox | Agent calls this to understand what code changed |
| PR diff | Forge-specific skill (GitHub: `gh pr diff`, GitLab: MR changes API) | Agent calls this to understand what code changed |
| Repository checkout | Full repo at PR HEAD | Checked out on the runner, mounted into the sandbox |
| Repo conventions | `AGENTS.md`, `CLAUDE.md`, `CONTRIBUTING.md` | Read from the checkout inside the sandbox |

Expand All @@ -84,7 +86,7 @@ The fix agent follows a similar pipeline to the [code agent](code.md), with an a
| Input | Source | How it gets there |
|-------|--------|-------------------|
| Human instruction | Free text after `/fs-fix` in the comment | Extracted by the workflow, passed as `HUMAN_INSTRUCTION` env var (up to 10,000 bytes) |
| PR diff | `gh pr diff` inside the sandbox | Same as bot-triggered |
| PR diff | Forge-specific skill | Same as bot-triggered |
| Repository checkout | Full repo at PR HEAD | Same as bot-triggered |
| Repo conventions | `AGENTS.md`, `CLAUDE.md`, `CONTRIBUTING.md` | Same as bot-triggered |
| Review body (if any) | Prior review bot `CHANGES_REQUESTED` review | Still injected as `review-body.txt`, but human instruction takes precedence |
Expand Down Expand Up @@ -115,7 +117,7 @@ than you might expect:
- **Other PR comments.** General discussion comments on the PR are not part of
the agent's input. Only the review body and the `/fs-fix` instruction are
read.
- **CI logs and check status.** The fix agent does not read GitHub Actions logs,
- **CI logs and check status.** The fix agent does not read CI logs,
check run output, or merge readiness indicators. It addresses review
feedback, not CI failures. (The [code agent](code.md) handles CI failures
during implementation.)
Expand All @@ -129,15 +131,15 @@ depends on where the URL points:

| URL type | Works? | Why |
|----------|--------|-----|
| Same-repo issue or PR (`#123` or full GitHub URL) | Yes | Resolved via the GitHub API |
| Same-repo issue or PR/MR (`#123` or full URL) | Yes | Resolved via the forge API (GitHub or GitLab) |
| Same-repo file or commit | Yes | Same mechanism |
| Cross-repo GitHub URL | No | Access is scoped to the target repo only |
| Cross-repo URL | No | Access is scoped to the target repo only |
| GitHub Gist | No | Not accessible from the agent environment |
| External URL (docs, pastebins, etc.) | No | External HTTP access is blocked |

GitHub may auto-shorten same-repo URLs in rendered comments (e.g.,
`https://github.com/org/repo/issues/2` becomes `#2`), but the full URL is
preserved either way.
`https://github.com/org/repo/issues/2` becomes `#2`). GitLab does not
auto-shorten URLs but the full URL is preserved either way.

**If you need the agent to act on external context**, paste the relevant
content directly into the `/fs-fix` comment rather than linking to it. The
Expand All @@ -155,6 +157,29 @@ The fix agent enforces iteration caps to prevent infinite review-fix loops:
- Each `/fs-fix` comment cancels any in-flight fix run for the same PR and
Comment thread
ggallen marked this conversation as resolved.
starts a new one.

## Multi-forge support

The fix agent supports both GitHub and GitLab. The harness `forge` block
selects the platform at runtime via `FULLSEND_FORGE`. On GitHub, the agent
uses `gh` for API access; on GitLab, it uses `curl` against the REST API.
Scripts dispatch forge-specific operations through `fix-ops.lib.sh`, and
forge-specific skills provide the appropriate CLI recipes.

### GitLab-specific variables

| Variable | Description |
|----------|-------------|
| `PR_URL` | Full HTTPS URL of the merge request. Used to derive `GITLAB_HOST` and validate `REPO_FULL_NAME`. |
| `GITLAB_TOKEN` | Personal or project access token with `api` scope. |

### GitLab host allowlist

`gitlab-fix-ops.lib.sh` validates `GITLAB_HOST` against an allowlist
(`gitlab.com`, `gitlab.cee.redhat.com`). To support a self-hosted instance,
add the host to `forge_validate_pr_url` in `gitlab-fix-ops.lib.sh`,
`ALLOWED_GITLAB_HOSTS` in `process-fix-result.py`, and
`policies/gitlab/fix.yaml`.

## Custom network policy

If this agent needs to reach hosts beyond the defaults, see the
Expand Down
2 changes: 1 addition & 1 deletion docs/network-policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ defaults. If multiple agents need the same custom hosts, create a
separate override for each one. For example, the
[code](code.md) and [fix](fix.md) agents both use
[`policies/base.yaml`](../policies/base.yaml)
(code agent also has a GitLab equivalent under `policies/gitlab/`).
(both agents also have GitLab equivalents under `policies/gitlab/`).

## Troubleshooting

Expand Down
3 changes: 3 additions & 0 deletions env/github/fix.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export PR_URL="${GITHUB_PR_URL}"
export GH_TOKEN="${GH_TOKEN}"
export FULLSEND_FORGE="github"
3 changes: 3 additions & 0 deletions env/gitlab/fix.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export PR_URL="${GITLAB_MR_URL}"
export GITLAB_TOKEN="${GITLAB_TOKEN}"
export FULLSEND_FORGE="gitlab"
59 changes: 43 additions & 16 deletions harness/fix.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
# agent : reads pre-fetched review body, fixes code, tests, scans, commits
# post_script : push commit, post summary comment on PR
#
# The agent NEVER pushes or creates PRs. Enforcement: the sandbox
# GH_TOKEN is read-only scoped, PUSH_TOKEN never enters the sandbox,
# and network policy restricts git/gh binary access.
# The agent NEVER pushes or creates PRs/MRs. Enforcement: the sandbox
Comment thread
ggallen marked this conversation as resolved.
# GH_TOKEN/GITLAB_TOKEN is read-only scoped, PUSH_TOKEN never enters the
# sandbox, and network policy restricts binary access per forge.
# Only the post-script, running on the runner with PUSH_TOKEN, can write.
agent: agents/fix.md
doc: docs/fix.md
Expand Down Expand Up @@ -62,6 +62,9 @@ env:
FIX_ITERATION: "${FIX_ITERATION}"
Comment thread
ggallen marked this conversation as resolved.
REVIEW_BODY_FILE: "${REVIEW_BODY_FILE}"
PRE_AGENT_HEAD: "${PRE_AGENT_HEAD}"
Comment thread
ggallen marked this conversation as resolved.
PUSH_TOKEN: "${PUSH_TOKEN}"
PUSH_TOKEN_SOURCE: "${PUSH_TOKEN_SOURCE}"
REPO_FULL_NAME: "${REPO_FULL_NAME}"
sandbox:
MAX_RETRIES: "1"
TIMEOUT_SECONDS: "1500"
Expand All @@ -70,6 +73,14 @@ env:
ITERATION_CAP_HUMAN: "10"
GOPATH: "/sandbox/go"
GOMODCACHE: "/sandbox/go/pkg/mod"
REPO_FULL_NAME: "${REPO_FULL_NAME}"
TRIGGER_SOURCE: "${TRIGGER_SOURCE}"
HUMAN_INSTRUCTION: "${HUMAN_INSTRUCTION}"
FIX_ITERATION: "${FIX_ITERATION}"
GIT_AUTHOR_NAME: "fullsend-fix"
GIT_AUTHOR_EMAIL: "${GIT_BOT_EMAIL}"
GIT_COMMITTER_NAME: "fullsend-fix"
GIT_COMMITTER_EMAIL: "${GIT_BOT_EMAIL}"

timeout_minutes: 25

Expand All @@ -80,22 +91,38 @@ forge:
openshell:
profiles:
- profiles/fullsend-github-code.yaml
pre_script: scripts/pre-fix.sh
post_script: scripts/post-fix.sh
skills:
- skills/github-forge
- skills/fix-review/github
host_files:
- src: env/github/fix.env
dest: /sandbox/workspace/.env.d/forge-fix.env
expand: true
env:
runner:
PUSH_TOKEN: "${PUSH_TOKEN}"
PUSH_TOKEN_SOURCE: "${PUSH_TOKEN_SOURCE}"
REPO_FULL_NAME: "${REPO_FULL_NAME}"
PR_NUMBER: "${PR_NUMBER}"
FULLSEND_FORGE: github
sandbox:
PR_NUMBER: "${PR_NUMBER}"
REPO_FULL_NAME: "${REPO_FULL_NAME}"
TRIGGER_SOURCE: "${TRIGGER_SOURCE}"
HUMAN_INSTRUCTION: "${HUMAN_INSTRUCTION}"
FIX_ITERATION: "${FIX_ITERATION}"
GH_TOKEN: "${GH_TOKEN}"
GIT_AUTHOR_NAME: "fullsend-fix"
GIT_AUTHOR_EMAIL: "${GIT_BOT_EMAIL}"
GIT_COMMITTER_NAME: "fullsend-fix"
GIT_COMMITTER_EMAIL: "${GIT_BOT_EMAIL}"
FULLSEND_FORGE: github
gitlab:
policy: policies/gitlab/fix.yaml
skills:
- skills/gitlab-forge
- skills/fix-review/gitlab
host_files:
- src: env/gitlab/fix.env
dest: /sandbox/workspace/.env.d/forge-fix.env
expand: true
env:
runner:
PR_NUMBER: "${MR_NUMBER}"
PR_URL: "${GITLAB_MR_URL}"
GITLAB_TOKEN: "${GITLAB_TOKEN}"
FULLSEND_FORGE: gitlab
sandbox:
PR_NUMBER: "${MR_NUMBER}"
PR_URL: "${GITLAB_MR_URL}"
GITLAB_TOKEN: "${GITLAB_TOKEN}"
FULLSEND_FORGE: gitlab
Loading
Loading