Skip to content

feat(risk): add GitLab support for risk assessment tier scripts - #981

Draft
maruiz93 wants to merge 18 commits into
fullsend-ai:mainfrom
maruiz93:922-risk-assessment-gitlab
Draft

feat(risk): add GitLab support for risk assessment tier scripts#981
maruiz93 wants to merge 18 commits into
fullsend-ai:mainfrom
maruiz93:922-risk-assessment-gitlab

Conversation

@maruiz93

Copy link
Copy Markdown
Contributor

Summary

Adds GitLab support for the risk assessment tier scripts.

  • risk-tier1.sh: Add forge-aware _fetch_pr_files_json() and _fetch_pr_meta() with GitLab API support via _gitlab_api_call(). Uses the paginated /diffs endpoint, includes host allowlist validation and overflow handling.
  • pre-review.src.sh: Add GitLab clone deepening branch using oauth2/gitlab-ci-token auth depending on token type.
  • post-review.src.sh: Replace deprecated fullsend post-comment with fullsend issues post-comment --tracker for forge-aware sticky comments.
  • Tests for all three components.

Stacked on #861 — this PR includes #861's commits until it merges, then the diff will auto-update to show only the GitLab changes.

Closes #922

Test plan

  • risk-tier1-test.sh — GitLab e2e test with curl stub, fallback test
  • pre-review-test.sh — GitLab clone deepening test with git stub
  • post-review-test.sh — Updated expected pattern for tracker-aware command
  • Manual: deploy with FULLSEND_FORGE=gitlab and verify risk signals on a GitLab MR

🤖 Generated with Claude Code

@maruiz93
maruiz93 requested a review from a team as a code owner August 24, 2026 08:01
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 24, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 8:03 AM UTC · Completed 8:40 AM UTC

Commit: 87d264b · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $6.40

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Add GitLab support and pipeline integration for PR risk assessment

✨ Enhancement 🧪 Tests 📝 Documentation ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Add risk assessment pre-pass (Tier 1/2/3) and persist results in review output JSON.
• Apply risk/* labels and a sticky risk comment during post-review processing.
• Add GitLab support for Tier 1 metadata collection and GitLab clone deepening.
Diagram

graph TD
  A["Review pipeline"] --> B["pre-review.sh"] --> C[("Git remote")]
  A --> D["pr-review orchestrator"] --> E["risk-assessment sub-agent"] --> F["risk-tier1.sh"] --> G{{"Forge APIs"}}
  D --> H["review-result schema"]
  A --> I["post-review.sh"]
  I --> J{{"Labels & comments"}}

  subgraph Legend
    direction LR
    _pipe["Pipeline step"] ~~~ _db[(External system)] ~~~ _ext{{External API}}
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Compute Tier 1 signals from local git diff only
  • ➕ Eliminates dependence on forge APIs and host allowlists
  • ➕ Works uniformly across forges without REST shape differences
  • ➖ Loses accurate per-file additions/deletions when the workspace isn’t the PR head
  • ➖ Harder to compute author/first-contribution metadata without API calls
  • ➖ Increases risk of incorrect signals in sandbox/runner environments
2. Move risk label/comment application into orchestrator output only (no post-review enforcement)
  • ➕ Keeps all decision-making in one place (orchestrator)
  • ➕ Simplifies post-review script logic
  • ➖ Still needs post-review to materialize labels/comments on the forge
  • ➖ Harder to reliably remove stale risk labels without deterministic post-step cleanup

Recommendation: Current approach is solid: Tier 1 uses forge APIs for authoritative metadata (with GitLab host allowlist + fallback behavior), Tier 2 is enabled by optional clone deepening, and post-review deterministically enforces labels/comments (including stale cleanup). The split keeps scoring flexible while making forge-side effects reliable and testable.

Files changed (23) +1662 / -13

Enhancement (7) +622 / -0
review-result.schema.jsonAdd risk_assessment schema and risk_signal definition +35/-0

Add risk_assessment schema and risk_signal definition

• Extends the review result schema with optional 'risk_assessment' (score/level/rationale plus optional tier signal arrays) and a reusable 'risk_signal' object definition. Enforces level enum and integer score bounds.

schemas/review-result.schema.json

post-review.shApply risk/* labels and post sticky risk comment +81/-0

Apply risk/* labels and post sticky risk comment

• Extends post-review processing to interpret 'risk_assessment', remove stale 'risk/*' labels, create/apply the appropriate risk label with color coding, and post a marker-based sticky risk comment via 'fullsend issues post-comment --tracker'. Includes sanitation/validation for score and level and a fallback path that removes stale risk labels when risk assessment is absent.

scripts/post-review.sh

post-review.src.shSource variant: apply risk/* labels and sticky risk comment +81/-0

Source variant: apply risk/* labels and sticky risk comment

• Mirrors 'post-review.sh' risk label + comment logic in the sourceable script variant, including control-label recognition, validation/sanitization, stale cleanup, and tracker-aware comment posting.

scripts/post-review.src.sh

pre-review.shDeepen shallow clone for Tier 2 risk analysis (GitHub/GitLab) +40/-0

Deepen shallow clone for Tier 2 risk analysis (GitHub/GitLab)

• Introduces optional clone deepening controlled by 'REVIEW_GIT_FETCH_DEPTH=0'. Supports GitHub via 'x-access-token' URL and GitLab via 'oauth2' (personal token) or 'gitlab-ci-token' (CI job token), deriving the host from 'GITLAB_HOST' or 'PR_URL' and warning on missing credentials.

scripts/pre-review.sh

pre-review.src.shSource variant: deepen shallow clone for Tier 2 risk analysis +40/-0

Source variant: deepen shallow clone for Tier 2 risk analysis

• Mirrors the clone-deepening logic in the sourceable script variant, including GitHub and GitLab authenticated fetch URL handling and warning-only failure behavior.

scripts/pre-review.src.sh

risk-assessment.mdAdd risk-assessment sub-agent definition +71/-0

Add risk-assessment sub-agent definition

• Defines a new synchronous sub-agent that runs Tier 1 metadata extraction, evaluates Tier 2 git-history signals, optionally uses Tier 3 issue context, and returns a raw JSON risk object (no findings).

skills/pr-review/sub-agents/risk-assessment.md

risk-tier1.shAdd forge-aware Tier 1 metadata signal script (GitHub + GitLab) +274/-0

Add forge-aware Tier 1 metadata signal script (GitHub + GitLab)

• Adds a deterministic Tier 1 metadata extractor that emits KEY=VALUE signals (change size, protected/security-sensitive paths, CI/deps/test ratio, author context). Implements forge-aware fetch functions with GitLab support via API v4 '/diffs' and MR metadata endpoints, including host allowlist enforcement and UNKNOWN fallback behavior on API failure.

skills/pr-risk-assessment/scripts/risk-tier1.sh

Tests (10) +706 / -0
annotations.yamlAdd eval expectations for low/moderate risk classification +26/-0

Add eval expectations for low/moderate risk classification

• Introduces an evaluation case asserting that a small docs/protected-path change should not be labeled elevated+ risk, and that protected-path downgrades prevent ready-for-merge labeling.

eval/review/cases/001-risk-low-typo-fix/annotations.yaml

input.yamlAdd eval fixture input for typo fix + CODEOWNERS change +22/-0

Add eval fixture input for typo fix + CODEOWNERS change

• Defines a synthetic GitHub PR fixture (README typo + '.github/CODEOWNERS') to drive the low-risk evaluation scenario.

eval/review/cases/001-risk-low-typo-fix/input.yaml

README.mdAdd repo fixture README for eval case 001 +7/-0

Add repo fixture README for eval case 001

• Adds the repository content used by the evaluation harness for the low-risk typo-fix scenario.

eval/review/cases/001-risk-low-typo-fix/repo/README.md

annotations.yamlAdd eval expectations for non-low risk on auth/security changes +22/-0

Add eval expectations for non-low risk on auth/security changes

• Introduces an evaluation case that forbids 'risk/low' for an auth/RBAC touching PR and asserts protected-path downgrade behavior.

eval/review/cases/002-risk-high-auth-change/annotations.yaml

input.yamlAdd eval fixture input for auth refactor scenario +65/-0

Add eval fixture input for auth refactor scenario

• Defines a synthetic multi-file PR fixture that touches auth and CODEOWNERS to drive moderate+ risk expectations.

eval/review/cases/002-risk-high-auth-change/input.yaml

README.mdAdd repo fixture README for eval case 002 +3/-0

Add repo fixture README for eval case 002

• Adds repository content used by the evaluation harness for the auth-change scenario.

eval/review/cases/002-risk-high-auth-change/repo/README.md

handler.goAdd repo fixture auth handler for eval case 002 +15/-0

Add repo fixture auth handler for eval case 002

• Adds an auth handler implementation used as base-branch content in the evaluation harness repo fixture.

eval/review/cases/002-risk-high-auth-change/repo/internal/auth/handler.go

post-review-test.shAdd control-label coverage and risk label/comment tests +96/-0

Add control-label coverage and risk label/comment tests

• Treats 'risk/*' as pipeline-managed control labels and adds extensive tests covering label creation, stale removal, invalid score/level warnings, and tracker-aware sticky comment posting.

scripts/post-review-test.sh

pre-review-test.shAdd GitLab clone-deepening test with git stub +92/-0

Add GitLab clone-deepening test with git stub

• Adds a GitLab-focused unit test that simulates a shallow repo and asserts 'git fetch --unshallow' uses an oauth2-style authenticated GitLab URL and logs a successful deepen message.

scripts/pre-review-test.sh

risk-tier1-test.shAdd unit and e2e tests for Tier 1 signal extraction +358/-0

Add unit and e2e tests for Tier 1 signal extraction

• Introduces a comprehensive bash test suite that sources 'risk-tier1.sh' and tests classification/matching helpers plus e2e runs. Includes GitHub e2e via 'gh' stub, GitLab e2e via 'curl' stub, failure fallback behavior, and a drift test ensuring protected-path defaults match 'harness/review.yaml'.

scripts/risk-tier1-test.sh

Documentation (4) +330 / -13
review.mdDocument risk_assessment field in agent output contract +1/-0

Document risk_assessment field in agent output contract

• Extends the review result field table to include the optional 'risk_assessment' object produced by the risk pre-pass.

agents/review.md

review.mdDocument risk labels and enablement flag +15/-1

Document risk labels and enablement flag

• Documents how 'risk/*' labels map to composite scores and clarifies that risk labels are informational. Adds 'REVIEW_RISK_ASSESSMENT_ENABLED' to the environment variable reference and updates the pipeline description to include risk assessment as a pre-pass.

docs/review.md

SKILL.mdAdd risk-assessment pre-pass step and linked-skill table +85/-12

Add risk-assessment pre-pass step and linked-skill table

• Updates the orchestrator skill documentation to define 'risk-assessment' as a pre-pass (step 3c-2), document enable/disable behavior, and generalize the linked-skill loading mechanism to include risk assessment.

skills/pr-review/SKILL.md

SKILL.mdAdd PR risk assessment scoring model documentation +229/-0

Add PR risk assessment scoring model documentation

• Introduces the three-tier scoring model (Tier 1 metadata / Tier 2 git history / Tier 3 linked issue) with weighting rules, dimension scoring guidance, degradation rules, and anchoring examples. Specifies the required JSON output contract for the risk sub-agent.

skills/pr-risk-assessment/SKILL.md

Other (2) +4 / -0
MakefileRun risk Tier 1 tests in script-test target +1/-0

Run risk Tier 1 tests in script-test target

• Adds 'scripts/risk-tier1-test.sh' to the 'script-test' Make target so CI/local runs execute the new Tier 1 unit/e2e tests.

Makefile

review.yamlEnable risk assessment skill and related env defaults +3/-0

Enable risk assessment skill and related env defaults

• Adds 'skills/pr-risk-assessment' to the harness skills list. Sets 'REVIEW_GIT_FETCH_DEPTH=0' on the runner and enables 'REVIEW_RISK_ASSESSMENT_ENABLED=true' in the sandbox by default.

harness/review.yaml

maruiz93 and others added 13 commits August 24, 2026 10:07
Deterministic bash script computing PR metadata signals (blast radius,
path sensitivity, CI impact, dependency risk, test coverage, author
context) for the risk assessment sub-agent. KEY=VALUE output format.

Refs: fullsend-ai/fullsend#4698
Signed-off-by: Marta Anon <manon@redhat.com>
SKILL.md defines the three-tier scoring model (metadata 50%, git
history 30%, linked issue 20%) with anchoring examples and output
format. Sub-agent definition dispatches as a synchronous pre-pass
using sonnet.

Refs: fullsend-ai/fullsend#4698
Signed-off-by: Marta Anon <manon@redhat.com>
Add risk_assessment to review-result.schema.json (optional field).
Add FULLSEND_RISK_ASSESSMENT_ENABLED to env/review.env (default true).
Update orchestrator: roster table, generalized skill-loading table
(replaces docs-currency special case), and new step 3c-2 for the
risk assessment pre-pass.

Refs: fullsend-ai/fullsend#4698
Signed-off-by: Marta Anon <manon@redhat.com>
Post-review.sh reads risk_assessment from the result JSON, applies a
risk/* label with color coding, and posts a sticky comment with the
score breakdown. Labels are idempotent — stale risk/* labels are
removed before applying the new one.

Refs: fullsend-ai/fullsend#4698
Signed-off-by: Marta Anon <manon@redhat.com>
Two review eval cases: a low-risk typo fix (expected risk/low label)
and a high-risk auth refactor (expected risk/high or risk/critical).
Exercises the full pipeline including risk sub-agent and post-review
label application.

Refs: fullsend-ai/fullsend#4698
Signed-off-by: Marta Anon <manon@redhat.com>
…rding

Replace word-splitting array assignment with mapfile to handle
filenames containing spaces. Fix 'Both' to 'All three' in non-standard
dispatch type documentation.

Refs: fullsend-ai/fullsend#4698
Signed-off-by: Marta Anon <manon@redhat.com>
- Add skills/pr-risk-assessment to harness skills array (sandbox access)
- Rename FULLSEND_RISK_ASSESSMENT_ENABLED to REVIEW_RISK_ASSESSMENT_ENABLED
- Add --paginate to PR files API call for PRs with >100 files
- Add RISK_SCORE integer 1-5 validation with warning fallback
- URL-encode AUTHOR in search query for defense-in-depth
- Read REVIEW_PROTECTED_PATHS env var with hardcoded fallback
- Add -e omission comment to risk-tier1.sh shell opts
- Add Glob to risk-assessment sub-agent tools list
- Add score/level consistency description to schema
- Document risk labels and REVIEW_RISK_ASSESSMENT_ENABLED in docs
- Add test coverage: invalid level/score, stale label removal

Signed-off-by: Marta Anon <maruiz93@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Marta Anon <manon@redhat.com>
.github/* matched all files under .github/ (CODEOWNERS, ISSUE_TEMPLATE,
etc.) as CI changes. Restrict to .github/workflows/* and .github/actions/*,
and add .gitlab-ci.yml, Jenkinsfile, azure-pipelines.yml patterns.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Marta Anon <manon@redhat.com>
Replace undocumented TARGET_REPO_DIR with the standard
REPO_DIR/GITHUB_WORKSPACE/target-repo fallback chain used by other
scripts. Add a warning when the target directory is not found instead
of silently skipping.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Marta Anon <manon@redhat.com>
Add if/then constraints so {score: 1, level: "critical"} is rejected
by schema validation. Each score value is tied to its canonical level.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Marta Anon <manon@redhat.com>
The requirements*.txt glob already covers the requirements.txt literal.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Marta Anon <manon@redhat.com>
Replace double jq-parse for author signals with single @TSV projection.
Add e2e test for multi-page pagination (jq -s 'add' merge).

Signed-off-by: Marta Anon <maruiz93@users.noreply.github.com>
Signed-off-by: Marta Anon <manon@redhat.com>
… fixes

- Fix compute_test_ratio to match on basename, so nested test_* files
  (e.g. tests/test_foo.py) are correctly detected
- Add risk_label_present eval judge to catch a broken risk pipeline
  (no-op assessment silently passes without it)
- Fix invalid risk level fallthrough: run stale-label cleanup when
  level fails validation instead of silently skipping
- Move rationale truncation into jq to avoid SIGPIPE under pipefail
- Add frontmatter qualifier to SKILL.md model instruction for consistency
- Document REVIEW_GIT_FETCH_DEPTH in docs/review.md Variables table

Signed-off-by: Marta Anon <maruiz93@users.noreply.github.com>
Signed-off-by: Marta Anon <manon@redhat.com>
@qodo-code-review

qodo-code-review Bot commented Aug 24, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Protected paths modified ✗ Dismissed 📜 Skill insight § Compliance
Description
This PR modifies protected governance/infrastructure paths (e.g., harness/, scripts/,
skills/), so it must not be auto-approved and requires explicit human review. Even with issue
linkage, this must be treated as approval-blocking for automation.
Code

harness/review.yaml[R18-21]

  - skills/pr-review
  - skills/code-review
  - skills/docs-review
+  - skills/pr-risk-assessment
Relevance

●●● Strong

Protected-path enforcement is an explicit governance control; PR modifies harness/scripts/skills
paths requiring human approval.

PR-#631

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 1538392 flags any modifications under protected paths as requiring human approval.
The diff modifies protected-path files including harness/review.yaml, scripts/pre-review.sh,
scripts/post-review.sh, and adds new content under skills/.

harness/review.yaml[17-38]
scripts/pre-review.sh[453-493]
scripts/post-review.sh[842-918]
skills/pr-risk-assessment/scripts/risk-tier1.sh[1-30]
Skill: pr-review

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Protected governance/infrastructure paths are modified in this PR, which requires explicit human review and must not be auto-approved.

## Issue Context
The compliance rule mandates raising a finding whenever protected paths are touched; providing clear justification in the PR description helps reviewers assess intent.

## Fix Focus Areas
- harness/review.yaml[17-22]
- scripts/pre-review.sh[453-493]
- scripts/post-review.sh[842-918]
- skills/pr-risk-assessment/scripts/risk-tier1.sh[1-274]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Unsanitized ::warning:: variables ✓ Resolved 📜 Skill insight ⛨ Security
Description
New ::warning:: workflow-command outputs interpolate values (e.g., _TARGET_DIR,
FULLSEND_FORGE, RISK_LEVEL) without applying the repository’s full _gha_sanitize control
(ANSI/control chars, ::, %0A/%0D). This can allow workflow-command injection or log corruption
in GitHub Actions contexts.
Code

scripts/pre-review.sh[460]

+    echo "::warning::Clone-deepening skipped — target directory '${_TARGET_DIR}' not found"
Relevance

●●● Strong

Team previously accepted sanitizing interpolated values before GitHub Actions workflow-command
output.

PR-#592

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 1538382 requires sanitizing every interpolated variable used in GitHub Actions
workflow commands. The diff adds new ::warning:: lines that interpolate _TARGET_DIR and
FULLSEND_FORGE without sanitization, and adds a custom RISK_LEVEL sanitization that omits the
repo’s ANSI-escape stripping present in _gha_sanitize().

scripts/pre-review.sh[458-467]
skills/pr-risk-assessment/scripts/risk-tier1.sh[182-202]
scripts/post-review.sh[47-47]
scripts/post-review.sh[847-870]
Skill: pr-review

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
New GitHub Actions workflow-command emissions (`::warning::...`) interpolate variables without applying the repository-standard sanitization.

## Issue Context
The repo already defines `_gha_sanitize()` to strip newlines, remove ANSI escapes, and escape `%` and `::`. New code paths should use the same sanitization for every interpolated value in workflow commands.

## Fix Focus Areas
- scripts/pre-review.sh[458-467]
- scripts/pre-review.src.sh[88-97]
- scripts/post-review.sh[852-870]
- scripts/post-review.src.sh[482-500]
- skills/pr-risk-assessment/scripts/risk-tier1.sh[182-202]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. GitLab token env mismatch ✓ Resolved 🐞 Bug ☼ Reliability
Description
risk-tier1.sh uses ${REVIEW_TOKEN} for GitLab API calls while the GitLab sandbox environment only
exports GITLAB_TOKEN, and the script runs with set -u so missing REVIEW_TOKEN aborts instead of
falling back to UNKNOWN. This breaks GitLab risk assessment execution in the sandbox.
Code

skills/pr-risk-assessment/scripts/risk-tier1.sh[R157-160]

+  curl --fail --silent --show-error \
+    --connect-timeout 10 --max-time 30 \
+    --header "PRIVATE-TOKEN: ${REVIEW_TOKEN}" \
+    "https://${host}/api/v4${endpoint}" "$@"
Relevance

●●● Strong

Team accepted fixing shell scripts using wrong/mismatched env var names causing set -u failures.

PR-#876

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The Tier-1 GitLab API call hard-references REVIEW_TOKEN, but the GitLab sandbox env (harness +
exported env file) only provides GITLAB_TOKEN, making the risk-tier1 script fail under set -u.

skills/pr-risk-assessment/scripts/risk-tier1.sh[144-160]
harness/review.yaml[104-115]
env/gitlab/review.env[1-8]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`skills/pr-risk-assessment/scripts/risk-tier1.sh` uses `REVIEW_TOKEN` for GitLab API auth. In the GitLab sandbox harness/env, only `GITLAB_TOKEN` is exported, so `set -u` causes an unbound-var failure and the script cannot produce Tier-1 signals.

### Issue Context
- GitLab sandbox env provides `GITLAB_TOKEN` but not `REVIEW_TOKEN`.
- The script claims to “always 0 — individual signal failures fall back to UNKNOWN”, but missing `REVIEW_TOKEN` violates this.

### Fix Focus Areas
- skills/pr-risk-assessment/scripts/risk-tier1.sh[144-160]
- harness/review.yaml[104-115]
- env/gitlab/review.env[1-8]

### Implementation notes
- In `_gitlab_api_call`, derive a token via `token="${REVIEW_TOKEN:-${GITLAB_TOKEN:-}}"`; if empty, return non-zero (so `main` falls back to UNKNOWN output).
- Optionally (preferred for consistency), also export `REVIEW_TOKEN="${GITLAB_TOKEN}"` into the GitLab sandbox env (harness + env file) so downstream scripts can uniformly rely on `REVIEW_TOKEN`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

4. Unvalidated GitLab fetch host ✓ Resolved 🐞 Bug ⛨ Security
Description
The GitLab clone-deepening path constructs a credentialed https URL using GITLAB_HOST or a
PR_URL-derived host without enforcing the same allowlist used by GitLab API calls. If GITLAB_HOST is
set to an unexpected value, the script may send the token to an unintended host during git fetch.
Code

scripts/pre-review.sh[R475-482]

+      _gl_host="${GITLAB_HOST:-}"
+      if [[ -z "${_gl_host}" && -n "${PR_URL:-}" ]]; then
+        _gl_host=$(echo "${PR_URL}" | sed -E 's|^https://([^/]+)/.*|\1|')
+      fi
+      if [[ -n "${_gl_token}" && -n "${_gl_host}" ]]; then
+        git -C "${_TARGET_DIR}" fetch --unshallow \
+          "https://${_gl_user}:${_gl_token}@${_gl_host}/${REPO_FULL_NAME}.git" 2>/dev/null \
+          && echo "Clone deepened successfully" \
Relevance

●●● Strong

Credentialed fetch URLs should enforce the same host allowlist used elsewhere; unvalidated host is a
real token-exposure risk.

PR-#38

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new deepening code uses _gl_host from GITLAB_HOST/PR_URL to form a credentialed fetch URL, while
the GitLab ops library demonstrates an explicit allowlist check for the host.

scripts/pre-review.sh[453-486]
scripts/lib/gitlab-review-ops.lib.sh[41-51]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`scripts/pre-review.sh` deepens shallow clones for GitLab using a URL that includes credentials. The host is taken from `GITLAB_HOST` (if set) or parsed from `PR_URL`, but the deepening block does not enforce an allowlist, unlike GitLab API operations.

### Issue Context
- GitLab API ops validate the MR host against an allowlist.
- The clone-deepening block introduces another credentialed network path that should match the same trust boundary.

### Fix Focus Areas
- scripts/pre-review.sh[475-486]
- scripts/lib/gitlab-review-ops.lib.sh[41-51]

### Implementation notes
- Before using `_gl_host`, validate it with the same allowlist (`gitlab.com|gitlab.cee.redhat.com`) and refuse (warn + skip deepening) if it’s not allowed.
- Prefer using the already-validated host derived from `forge_validate_pr_url`/`forge_parse_pr_url` (e.g., reuse `GITLAB_HOST` set by parse), and ignore externally supplied `GITLAB_HOST` overrides unless they pass allowlist.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. GitLab diffs not paginated ✓ Resolved 🐞 Bug ≡ Correctness
Description
The GitLab implementation of _fetch_pr_files_json fetches only a single page from the /diffs
endpoint (per_page=100) despite comments stating it is paginated. For MRs with >100 changed files,
risk-tier1 undercounts files/lines and can misclassify blast radius and other signals.
Code

skills/pr-risk-assessment/scripts/risk-tier1.sh[R172-176]

+      # /diffs is the paginated replacement for /changes (deprecated since 15.7).
+      # GitLab returns raw unified diffs, not pre-computed counts like GitHub,
+      # so we parse +/- lines (excluding +++ and --- headers) to match the shape.
+      _gitlab_api_call "/projects/${repo_encoded}/merge_requests/${PR_NUMBER}/diffs?per_page=100" 2>/dev/null \
+        | jq '[.[] | {
Relevance

●●● Strong

Single-page fetch despite pagination comment is a real correctness bug causing undercount on large
MRs.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The code comments claim /diffs is paginated, but the implementation issues a single request with
per_page=100 and no page iteration, unlike the GitHub --paginate path.

skills/pr-risk-assessment/scripts/risk-tier1.sh[163-180]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`_fetch_pr_files_json()` for GitLab calls `/merge_requests/<iid>/diffs?per_page=100` once, but does not follow pages. Large MRs will produce incomplete file lists and incorrect FILES_CHANGED/LINES_CHANGED computations.

### Issue Context
GitHub path uses `gh api --paginate ...`, but GitLab path does not loop pages.

### Fix Focus Areas
- skills/pr-risk-assessment/scripts/risk-tier1.sh[169-180]

### Implementation notes
- Add a pagination loop (`page=1..N`) calling `/diffs?per_page=100&page=$page` until an empty array is returned.
- Accumulate arrays across pages (e.g., append JSON arrays then `jq -s 'add'`), then compute additions/deletions across the combined set.
- Consider a sane max-pages cap to avoid unbounded loops; on cap hit, return non-zero so `main` emits UNKNOWN (or emit a warning signal if you prefer).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
✅ Compliance rules (platform): 56 rules
✅ Skills: 4 invoked
  code-review
  code-implementation
  pr-review
  docs-review

Grey Divider

Tip of the day
💡 Did you know, you can switch off images and animations for a plain-text comment

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread scripts/pre-review.sh Outdated
Comment thread harness/review.yaml
Comment thread skills/pr-risk-assessment/scripts/risk-tier1.sh
Comment thread skills/pr-risk-assessment/scripts/risk-tier1.sh Outdated
Comment thread scripts/pre-review.sh
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 24, 2026

Copy link
Copy Markdown

Review

Findings

Critical

  • [duplicate-execution] scripts/post-review.sh, scripts/post-review.src.sh — The risk assessment block (label application + sticky comment posting) is added twice in each file: once before forge_post_review and once after outcome labels. Both blocks execute unconditionally in the same script run, causing: (1) risk labels applied/removed twice per run, (2) two sticky comments posted via different CLI commands (fullsend post-comment --repo in Block 1 vs fullsend issues post-comment --tracker in Block 2), (3) Block 1 uses the deprecated GitHub-only CLI that will fail on GitLab, while Block 2 uses the forge-aware CLI, (4) behavioral divergence between blocks — Block 1 uses remove_stale_risk_labels helper + jq [0:2000] string slicing for rationale truncation, Block 2 inlines the stale-label loop + uses head -c 2000 (byte-count truncation that can split multi-byte UTF-8 characters). The test at line 468 asserts only the Block 2 form (fullsend issues post-comment --tracker github), leaving Block 1's invocation untested. Independently identified by correctness, security, intent-coherence, and style-conventions sub-agents.
    Remediation: Remove Block 2 (the post-outcome-labels copy) from both post-review.sh and post-review.src.sh. Keep only Block 1 (pre-forge_post_review), which already handles all cases including stale label removal when risk assessment is absent. Update Block 1's comment-posting command to use the forge-aware form: fullsend issues post-comment --tracker "${FULLSEND_FORGE}" --project "${REPO}" ....

Medium

  • [protected-path] 13 files under protected paths (agents/, harness/, scripts/, skills/) — This PR modifies governance and infrastructure files that require human approval. The PR links to issue Add GitLab support for risk assessment tier scripts #922 and explains the rationale (adding GitLab support for risk assessment). Human approval is always required for protected-path changes, regardless of context.

Low

  • [test-inadequate] scripts/post-review-test.sh — GitLab forge risk tests cover only two positive scenarios (comment posted, review posted via tracker). Missing: risk label removal when risk_assessment is absent, invalid risk levels, and different risk levels on GitLab. The GitHub tests cover these edge cases comprehensively but GitLab-specific paths are not exercised.
    Remediation: Add GitLab forge tests for risk-absent stale label removal, invalid risk level, and at least one additional risk level.

  • [host-validation-inconsistency] scripts/pre-review.sh, skills/pr-risk-assessment/scripts/risk-tier1.sh — Clone-deepening and _gitlab_api_call() use a hardcoded host allowlist (gitlab.com|gitlab.cee.redhat.com) while other GitLab API code in the codebase uses _validate_gitlab_host() which validates against CI_SERVER_HOST dynamically. The hardcoded list is fail-closed (safe), but adding a new GitLab instance requires updating multiple hardcoded lists rather than a single validation function.
    Remediation: Use _validate_gitlab_host where available, or document the deliberate redundancy and add a drift test.

  • [naming-convention] scripts/post-review-test.sh — Several test names are truncated with ellipsis (e.g., risk-a..., risk-l..., risk-g..., risk-i...). All other test names use full descriptive kebab-case identifiers. Four distinct tests all print risk-i..., making failures impossible to triage from CI logs.
    Remediation: Use full descriptive names matching existing patterns.

  • [scope-creep] skills/pr-risk-assessment/SKILL.md — Issue Add GitLab support for risk assessment tier scripts #922 authorizes GitLab support for risk assessment. Approximately half the diff (12+ files) is the entire risk assessment feature from stacked base PR feat(risk): add PR risk assessment scoring to review pipeline #861. The PR description acknowledges this (Stacked on #861). Not unauthorized work, but most design surface area belongs to feat(risk): add PR risk assessment scoring to review pipeline #861's scope.

  • [schema-backward-compatibility] schemas/review-result.schema.json — The risk_assessment field is added as an optional property. The schema uses additionalProperties: false, so downstream consumers who have vendored a prior version of this schema will reject payloads containing the new field until they update their copy. This is the expected behavior for strict schemas and not a breaking change to existing payloads.

  • [docs-currency] docs/review.md — The paragraph below the Variables table says "Override either variable" but the table now contains six variables. The wording was stale before this PR (three variables existed), and the PR widens the gap by adding two more.
    Remediation: Change "Override either variable" to "Override any of these variables".


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run

Review

Findings

Medium

  • [protected-path] 13 files under protected paths (agents/, harness/, scripts/, skills/) — This PR modifies governance and infrastructure files that require human approval. The PR links to issue Add GitLab support for risk assessment tier scripts #922 and explains the rationale (adding GitLab support for risk assessment). Human approval is always required for protected-path changes, regardless of context.

Low

  • [test-inadequate] scripts/post-review-test.sh — GitLab forge tests for risk label application cover only two positive scenarios (comment posted, review posted via tracker). Missing: risk label removal when risk_assessment is absent, invalid risk levels, and different risk levels on GitLab. The GitHub tests cover these edge cases comprehensively but GitLab-specific paths are not exercised.
    Remediation: Add GitLab forge tests for risk-absent stale label removal, invalid risk level, and at least one additional risk level.

  • [workflow-command-injection] scripts/post-review.sh:869RISK_LEVEL is interpolated into a ::warning:: message after stripping newlines, %, and : characters, but ANSI escape sequences are not stripped. The _gha_sanitize() function used elsewhere also strips ANSI. GHA workflow command injection is prevented (: is stripped), but ANSI sequences could reach log output. Same pattern in post-review.src.sh.
    Remediation: Use _gha_sanitize for the warning message.

  • [naming-convention] scripts/post-review-test.sh — Several test names are truncated with ellipsis (e.g., risk-a..., risk-l..., risk-g...). All other test names use full descriptive kebab-case identifiers.
    Remediation: Use full descriptive names matching existing patterns.

  • [scope-creep] harness/review.yaml — The diff adds .pi/ to REVIEW_PROTECTED_PATHS. Not authorized by issue Add GitLab support for risk assessment tier scripts #922; inherited from stacked base PR feat(risk): add PR risk assessment scoring to review pipeline #861.

  • [code-organization] scripts/post-review.sh — Risk assessment label/comment block (~80 lines) duplicated verbatim between post-review.sh and post-review.src.sh. Follows existing .sh/.src.sh convention.

Previous run (2)

Review

Findings

Medium

  • [api-contract] skills/pr-risk-assessment/scripts/risk-tier1.sh — The _gitlab_api_call function uses a hardcoded host allowlist (gitlab.com|gitlab.cee.redhat.com) instead of the shared _validate_gitlab_host function from gitlab-host-validation.lib.sh used by the rest of the codebase. New GitLab instances added to policies will silently fail tier-1 signal collection (falling back to UNKNOWN values). The comment references gitlab-review-ops.lib.sh but that lib validates dynamically against CI_SERVER_HOST.
    Remediation: Extract the allowlist to a shared constant, or forward CI_SERVER_HOST to the sandbox via harness/review.yaml env.sandbox.

  • [protected-path] 13 files under protected paths (agents/, harness/, scripts/, skills/) — This PR modifies governance and infrastructure files that require human approval. The PR links to issue Add GitLab support for risk assessment tier scripts #922 and explains the rationale for these changes (GitLab support for risk assessment). Human approval is always required for protected-path changes, regardless of context.

Low

  • [silent-data-truncation] skills/pr-risk-assessment/scripts/risk-tier1.sh — The GitLab _fetch_pr_files_json requests per_page=100 without pagination. For MRs with >100 changed files, signals are based on an incomplete file set. The GitHub path uses gh api --paginate. In practice, MRs exceeding 100 files are rare and the signals would still be directionally correct, but parity with the GitHub path is worth considering.

  • [error-handling-gap] skills/pr-risk-assessment/scripts/risk-tier1.sh — If the GitLab API returns valid JSON with unexpected structure (e.g., an error object instead of an array), jq silently produces an empty array causing FILES_CHANGED=0 instead of the intended UNKNOWN fallback. The curl --fail flag covers HTTP errors, but proxy/gateway 200 responses with error bodies would pass through.

  • [test-inadequate] scripts/post-review-test.sh — Risk assessment label tests cover GitHub forge only. No GitLab forge tests exist for risk label application and sticky comment posting via fullsend issues post-comment --tracker.

  • [workflow-command-injection] scripts/pre-review.sh — Clone-deepening emits ::warning:: messages with unsanitized _TARGET_DIR (derived from REPO_DIR/GITHUB_WORKSPACE). This runs on the runner where GHA processes workflow commands. While these variables are infrastructure-controlled, the pattern is inconsistent with the rest of the script which uses _gha_sanitize.
    Remediation: Wrap with _gha_sanitize. Apply the same fix to scripts/pre-review.src.sh.

@fullsend-ai-review fullsend-ai-review Bot added the requires-manual-review Review requires human judgment label Aug 24, 2026
Move the risk assessment label section before forge_post_review so
risk/* labels land even when the review submission fails — e.g. when
GitHub returns 422 for a self-review (same token creates and reviews
the PR, as happens in eval environments).

Previously, a 422 on REQUEST_CHANGES caused post-review.sh to exit
before reaching the risk label block, leaving cases 001 and 002
without risk/* labels and failing the risk_label_present eval judge.

Signed-off-by: Marta Anon <marta@redhat.com>
Signed-off-by: Marta Anon <manon@redhat.com>
@maruiz93

Copy link
Copy Markdown
Contributor Author

Re: api-contract (hardcoded host allowlist) — the duplication is intentional. risk-tier1.sh runs inside the agent sandbox where host-side libraries (gitlab-review-ops.lib.sh) are not available. The comment on L154 documents this: "Defense-in-depth: mirror the allowlist in gitlab-review-ops.lib.sh". All three instances (risk-tier1.sh, pre-review.src.sh, gitlab-review-ops.lib.sh) are verified to match.

@maruiz93

Copy link
Copy Markdown
Contributor Author

Addressed in baf841c:

  • [api-contract] The hardcoded allowlist is intentional — risk-tier1.sh runs inside the agent sandbox where the host-side libs (gitlab-review-ops.lib.sh, gitlab-host-validation.lib.sh) aren't available. The sandbox is isolated by design. Both allowlists now match (gitlab.com|gitlab.cee.redhat.com).

  • [silent-data-truncation] Added pagination loop (max 10 pages = 1000 files) with page accumulation via jq -s 'add', matching the GitHub path's --paginate behavior.

  • [error-handling-gap] Added JSON array type validation (jq -e 'type == "array"') before processing each page. Unexpected response shapes (proxy/gateway error objects) now break the loop. Also added a got_data flag so the UNKNOWN fallback triggers when no pages succeed.

  • [test-inadequate] Added GitLab risk label tests to post-review-test.sh: risk-gitlab-comment-tracker (verifies --tracker gitlab) and risk-gitlab-post-review-forge (verifies --forge gitlab).

  • [workflow-command-injection] Added _gha_sanitize to all interpolated values in ::warning:: commands across both pre-review.src.sh and risk-tier1.sh.

@fullsend-ai-review

fullsend-ai-review Bot commented Aug 24, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 10:08 AM UTC · Ended 10:18 AM UTC

Commit: baf841c · View workflow run →

Extract remove_stale_risk_labels() helper to deduplicate risk-label
removal loops, fix return→exit 0 in pagination test stub, and add
array-type guard for gh API edge case in risk-tier1.sh.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Marta Anon <manon@redhat.com>
@maruiz93
maruiz93 force-pushed the 922-risk-assessment-gitlab branch from baf841c to b19d023 Compare August 24, 2026 10:18
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 24, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 10:20 AM UTC · Ended 10:22 AM UTC

Commit: b19d023 · View workflow run →

@maruiz93
maruiz93 force-pushed the 922-risk-assessment-gitlab branch from b19d023 to 6f112b6 Compare August 24, 2026 10:21
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 24, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 10:24 AM UTC · Completed 10:43 AM UTC

Commit: 6f112b6 · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $9.03

maruiz93 and others added 2 commits August 24, 2026 12:36
- Add skills/pr-risk-assessment to harness skills array (sandbox access)
- Rename FULLSEND_RISK_ASSESSMENT_ENABLED to REVIEW_RISK_ASSESSMENT_ENABLED
- Add --paginate to PR files API call for PRs with >100 files
- Add RISK_SCORE integer 1-5 validation with warning fallback
- URL-encode AUTHOR in search query for defense-in-depth
- Read REVIEW_PROTECTED_PATHS env var with hardcoded fallback
- Add -e omission comment to risk-tier1.sh shell opts
- Add Glob to risk-assessment sub-agent tools list
- Add score/level consistency description to schema
- Document risk labels and REVIEW_RISK_ASSESSMENT_ENABLED in docs
- Add test coverage: invalid level/score, stale label removal

Signed-off-by: Marta Anon <maruiz93@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Marta Anon <manon@redhat.com>
Make risk-tier1.sh forge-aware with _fetch_pr_files_json() and
_fetch_pr_meta() that dispatch on FULLSEND_FORGE. GitLab path uses
curl against the MR API with diff-text parsing for line counts and
first_contribution field for author signals.

Add GitLab clone deepening in pre-review using oauth2 token auth,
with host derived from PR_URL when GITLAB_HOST is unset.

Replace deprecated fullsend post-comment with fullsend issues
post-comment --tracker for forge-aware sticky risk comments.

Closes fullsend-ai#922

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Marta Anon <manon@redhat.com>
fullsend-ai-review[bot]

This comment was marked as outdated.

…lback, add pagination

- Sanitize ::warning:: interpolations with _gha_sanitize (pre-review.src.sh, risk-tier1.sh)
- Fall back to GITLAB_TOKEN when REVIEW_TOKEN is unset in sandbox (risk-tier1.sh)
- Add REVIEW_TOKEN to GitLab sandbox env in harness for consistency
- Paginate GitLab /diffs endpoint with max 10 pages (risk-tier1.sh)
- Validate JSON response is an array before processing diffs
- Enforce host allowlist on clone-deepening credentialed URL (pre-review.src.sh)
- Fix shellcheck SC2005 and git identity in CI test (pre-review-test.sh)
- Add GitLab token fallback and pagination-aware test stubs (risk-tier1-test.sh)
- Add GitLab forge tests for risk label and tracker-aware comment (post-review-test.sh)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Marta Anon <manon@redhat.com>
@maruiz93
maruiz93 force-pushed the 922-risk-assessment-gitlab branch from 6f112b6 to f00ffcb Compare August 24, 2026 10:45
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 24, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 10:46 AM UTC · Completed 11:05 AM UTC

Commit: f00ffcb · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $10.46

@fullsend-ai-review fullsend-ai-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: The following review comments could not be posted on the diff (GitHub returned 422) and are included here instead:

  • scripts/post-review-test.sh (file-level): Line 478 · [low] naming-convention

Several test names are truncated with ellipsis (risk-a..., risk-l..., risk-g..., risk-i...). Four distinct tests all print risk-i..., making failures impossible to triage from CI logs. All other tests use full descriptive kebab-case names.

Suggested fix: Use full descriptive names matching existing patterns.

  • docs/review.md (file-level): Line 95 · [low] docs-currency

The paragraph below the Variables table says 'Override either variable' but the table now contains six variables. The PR widens a pre-existing staleness gap by adding two more variables.

Suggested fix: Change 'Override either variable' to 'Override any of these variables'.

@fullsend-ai-review fullsend-ai-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the review comment for full details.

Comment thread scripts/pre-review.sh
if [[ -z "${_gl_host}" && -n "${PR_URL:-}" ]]; then
_gl_host=$(echo "${PR_URL}" | sed -E 's|^https://([^/]+)/.*|\1|')
fi
if [[ -n "${_gl_token}" && -n "${_gl_host}" ]]; then

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[low] host-validation-inconsistency

Clone-deepening and _gitlab_api_call() use a hardcoded host allowlist while other GitLab API code uses _validate_gitlab_host() which validates against CI_SERVER_HOST dynamically. Fail-closed (safe) but creates maintenance risk when onboarding new GitLab instances.

Suggested fix: Use _validate_gitlab_host where available, or document the deliberate redundancy.

@fullsend-ai-review fullsend-ai-review Bot removed the requires-manual-review Review requires human judgment label Aug 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add GitLab support for risk assessment tier scripts

1 participant