Skip to content
Open
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
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ script-test:
$(call run-timed,bash scripts/post-scribe-test.sh)
$(call run-timed,bash scripts/validate-output-schema-test.sh)
$(call run-timed,bash scripts/validate-code-output-test.sh)
$(call run-timed,bash schemas/review-result-label-actions-test.sh)
$(call run-timed,bash scripts/gitlint-forbidden-type-scope-test.sh)
$(call run-timed,bash hack/lint-agent-docs-test.sh)
$(call run-timed,bash eval/lint-measurements-test.sh)
Expand Down
77 changes: 49 additions & 28 deletions agents/review.md
Original file line number Diff line number Diff line change
Expand Up @@ -243,39 +243,45 @@ fields such as `outcome`, `summary`, `prior_review_sha`, or

**Top-level object** (`additionalProperties: false`):

| Field | Type | Always required | Description |
|-------------|---------|-----------------|--------------------------------------------------|
| `action` | string | yes | One of: `approve`, `request-changes`, `comment`, `reject`, `failure` |
| `pr_number` | integer | yes | PR number (minimum 1) |
| `repo` | string | yes | `owner/repo` format (pattern: `^[^/]+/[^/]+$`) |
| `head_sha` | string | conditional | Commit SHA (40 or 64 hex chars) |
| `body` | string | conditional | Markdown review comment (min 1 char) |
| `findings` | array | conditional | Array of finding objects (min 1 item when present)|
| `reason` | string | conditional | One of: `tool-failure`, `missing-context`, `ambiguous-findings`, `token-limit` |
| `label_actions` | object | no | Contextual label recommendations (see `issue-labels` skill) |
| `risk_assessment` | object | no | Risk assessment from the pre-pass sub-agent (see `pr-risk-assessment` skill) |
| Field | Type | Always required | Description |
|-------------------|---------|-----------------|--------------------------------------------------------------------------------|
| `action` | string | yes | One of: `approve`, `request-changes`, `comment`, `reject`, `failure` |
| `pr_number` | integer | yes | PR number (minimum 1) |
| `repo` | string | yes | `owner/repo` format (pattern: `^[^/]+/[^/]+$`) |
| `head_sha` | string | conditional | Commit SHA (40 or 64 hex chars) |
| `body` | string | conditional | Markdown review comment (min 1 char) |
| `findings` | array | conditional | Array of finding objects (min 1 item when present) |
| `reason` | string | conditional | One of: `tool-failure`, `missing-context`, `ambiguous-findings`, `token-limit` |
| `label_actions` | object | no | Contextual label recommendations (see `issue-labels` skill) |
| `risk_assessment` | object | no | Risk assessment from the pre-pass sub-agent (see `pr-risk-assessment` skill) |

**Required fields per action:**

| Action | Required fields |
|-------------------|------------------------------------------|
| `approve` | `body`, `head_sha` |
| `request-changes` | `body`, `head_sha`, `findings` |
| `comment` | `body`, `head_sha` |
| `reject` | `body`, `head_sha`, `findings` |
| `failure` | `reason` |
| Action | Required fields |
|-------------------|--------------------------------|
| `approve` | `body`, `head_sha` |
| `request-changes` | `body`, `head_sha`, `findings` |
| `comment` | `body`, `head_sha` |
| `reject` | `body`, `head_sha`, `findings` |
| `failure` | `reason` |

**Finding object** (`additionalProperties: false`):

| Field | Type | Required | Description |
|---------------|---------|----------|-----------------------------------------------|
| `severity` | string | yes | One of: `critical`, `high`, `medium`, `low`, `info` |
| `category` | string | yes | Finding category (min 1 char) |
| `file` | string | yes | File path (min 1 char) |
| `line` | integer | no | Line number (minimum 1) |
| `description` | string | yes | Finding description (min 1 char) |
| `remediation` | string | no | Suggested fix |
| `actionable` | boolean | no | When true with a non-empty `remediation`, routes the verdict to `request-changes` so the fix agent can address the finding automatically (follow-up issue creation is temporarily disabled; see #1137) |
| Field | Type | Required | Description |
|-----------------------|---------|----------|----------------------------------------------------------------------|
| `severity` | string | yes | One of: `critical`, `high`, `medium`, `low`, `info` |
| `category` | string | yes | Finding category (min 1 char) |
| `file` | string | yes | File path (min 1 char) |
| `line` | integer | no | Line number (minimum 1) |
| `description` | string | yes | Finding description (min 1 char) |
| `remediation` | string | no | Suggested fix |
| `actionable` | boolean | no | If `true`, marks the finding for future follow-up issue creation[1] |
| `verified_variables` | array | yes | Variables confirmed as having the security control applied[2][3] |
| `unchecked_variables` | array | yes | Variables not confirmed as having the security control applied[3][2] |

[1] *Only* for low/info findings in an `approve` result
[2] *Required* in findings that identify a sanitization or security control function
[3] Use `[]` for non-security findings

Schema validation failures trigger a harness retry iteration. The jq
examples below show the exact JSON shape for each action.
Expand All @@ -296,14 +302,29 @@ jq -n \

For `request-changes` (including actionable low/info findings) or `reject`:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[high] logic-error

The jq example heading says 'For request-changes (including actionable low/info findings) or reject:' but the jq command uses --arg action "approve". This heading/content mismatch could cause an agent following the first template to emit action: "approve" when it intends to emit request-changes.

Suggested fix: Change the heading at line 303 to 'For approve with actionable low/info findings:' to match the --arg action "approve" in the jq example below it.


```bash
jq -n \
--arg action "approve" \
--argjson pr_number <number> \
--arg repo "<owner/repo>" \
--arg head_sha "<sha>" \
--arg body "<markdown review comment>" \
--argjson findings '[{"severity":"low","category":"<category>","file":"<path>","description":"<desc>","actionable":true,"verified_variables":["<var1>"],"unchecked_variables":["<var2>"]}]' \
'{action: $action, pr_number: $pr_number, repo: $repo,
head_sha: $head_sha, body: $body, findings: $findings}' \
> "$FULLSEND_OUTPUT_DIR/agent-result.json"
```

For `request-changes` or `reject`:

```bash
jq -n \
--arg action "<request-changes|reject>" \
--argjson pr_number <number> \
--arg repo "<owner/repo>" \
Comment thread
ben-alkov marked this conversation as resolved.
--arg head_sha "<sha>" \
--arg body "<markdown review comment>" \
--argjson findings '<findings array>' \
--argjson findings '[{"severity":"high","category":"<category>","file":"<path>","description":"<desc>","verified_variables":[],"unchecked_variables":[]}]' \
'{action: $action, pr_number: $pr_number, repo: $repo,
head_sha: $head_sha, body: $body, findings: $findings}' \
> "$FULLSEND_OUTPUT_DIR/agent-result.json"
Expand Down
168 changes: 168 additions & 0 deletions schemas/review-result-label-actions-test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
#!/usr/bin/env bash

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[medium] missing-test

New test file is not registered in the Makefile's script-test target. All other test scripts are listed there and run by the script-test.yml CI workflow. This file will not execute in CI.

Suggested fix: Add $(call run-timed,bash schemas/review-result-label-actions-test.sh) to the script-test target in the Makefile.

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] naming-convention

Test file placed in schemas/ directory, which contains only .schema.json files. Convention places test scripts in scripts/.

Suggested fix: Move to scripts/review-result-label-actions-test.sh.

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] naming-convention

Test script placed in schemas/ directory which on the base branch contains only .schema.json files. All existing test scripts reside in scripts/, hack/, eval/scripts/, or .github/scripts/. The test also uses a different validation pattern (Draft202012Validator) than the established harness validator.

Suggested fix: Move to scripts/review-result-label-actions-test.sh and adapt to use the established validation pattern.

# Tests for label_actions support in review-result.schema.json
set -euo pipefail

SCHEMA="$(cd "$(dirname "$0")" && pwd)/review-result.schema.json"
FAILURES=0

fail() {
echo "FAIL: $1"
FAILURES=$((FAILURES + 1))
}

validate() {
local desc="$1"
local json="$2"
local expect_pass="$3"

if echo "${json}" | python3 -c "
import sys, json
from jsonschema import validate, ValidationError, Draft202012Validator
schema = json.load(open('${SCHEMA}'))
instance = json.load(sys.stdin)
Draft202012Validator(schema).validate(instance)
sys.exit(0)
" 2>/dev/null; then
if [ "${expect_pass}" = "true" ]; then
echo "PASS: ${desc}"
else
fail "${desc} (expected rejection but schema accepted it)"
fi
else
if [ "${expect_pass}" = "false" ]; then
echo "PASS: ${desc}"
else
fail "${desc} (expected acceptance but schema rejected it)"
fi
fi
}

# 1. approve without label_actions (baseline)
validate "approve-without-label-actions" '{
"action": "approve",
"pr_number": 42,
"repo": "org/repo",
"head_sha": "abcdef0123456789abcdef0123456789abcdef01",
"body": "Looks good to me."
}' true

# 2. approve with valid label_actions
validate "approve-with-label-actions" '{
"action": "approve",
"pr_number": 42,
"repo": "org/repo",
"head_sha": "abcdef0123456789abcdef0123456789abcdef01",
"body": "Looks good to me.",
"label_actions": {
"reason": "Approved PR, adding reviewed label",
"actions": [
{ "action": "add", "label": "reviewed" }
]
}
}' true

# 3. request-changes with label_actions
validate "request-changes-with-label-actions" '{
"action": "request-changes",
"pr_number": 42,
"repo": "org/repo",
"head_sha": "abcdef0123456789abcdef0123456789abcdef01",
"body": "Please fix the issues.",
"findings": [
{
"severity": "high",
"category": "security",
"file": "main.go",
"description": "SQL injection vulnerability",
"verified_variables": [],
"unchecked_variables": []
}
],
"label_actions": {
"reason": "Security issue found, flagging for review",
"actions": [
{ "action": "add", "label": "security" },
{ "action": "remove", "label": "needs-review" }
]
}
}' true

# 4. failure with label_actions
validate "failure-with-label-actions" '{
"action": "failure",
"pr_number": 42,
"repo": "org/repo",
"reason": "tool-failure",
"label_actions": {
"reason": "Tool failure, marking for manual review",
"actions": [
{ "action": "add", "label": "needs-manual-review" }
]
}
}' true

# 5. label_actions missing reason — should fail
validate "label-actions-missing-reason" '{
"action": "approve",
"pr_number": 42,
"repo": "org/repo",
"head_sha": "abcdef0123456789abcdef0123456789abcdef01",
"body": "LGTM",
"label_actions": {
"actions": [
{ "action": "add", "label": "reviewed" }

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] pattern-inconsistency

Uses python3/jsonschema for schema validation while the established pattern (validate-output-schema-test.sh) delegates to validate-output-schema.sh, which provides diagnostic features like allowed-properties error messages.

]
}
}' false

# 6. label_actions with empty actions array — should fail
validate "label-actions-empty-actions" '{
"action": "approve",
"pr_number": 42,
"repo": "org/repo",
"head_sha": "abcdef0123456789abcdef0123456789abcdef01",
"body": "LGTM",
"label_actions": {
"reason": "No labels to change",
"actions": []
}
}' false

# 7. label_actions with invalid action verb — should fail
validate "label-actions-invalid-verb" '{
"action": "approve",
"pr_number": 42,
"repo": "org/repo",
"head_sha": "abcdef0123456789abcdef0123456789abcdef01",
"body": "LGTM",
"label_actions": {
"reason": "Replace a label",
"actions": [
{ "action": "replace", "label": "old-label" }
]
}
}' false

# 8. label_actions with extra property — should fail
validate "label-actions-extra-property" '{
"action": "approve",
"pr_number": 42,
"repo": "org/repo",
"head_sha": "abcdef0123456789abcdef0123456789abcdef01",
"body": "LGTM",
"label_actions": {
"reason": "Adding label",
"actions": [
{ "action": "add", "label": "reviewed" }
],
"priority": "high"
}
}' false

echo ""
if [ "${FAILURES}" -gt 0 ]; then
echo "${FAILURES} test(s) failed."
exit 1
else
echo "All tests passed."
fi
14 changes: 12 additions & 2 deletions schemas/review-result.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@
"$defs": {
"finding": {
"type": "object",
"required": ["severity", "category", "file", "description"],
"required": ["severity", "category", "file", "description", "verified_variables", "unchecked_variables"],
Comment thread
ben-alkov marked this conversation as resolved.
Comment thread
qodo-code-review[bot] marked this conversation as resolved.
Comment thread
ben-alkov marked this conversation as resolved.
Comment thread
ben-alkov marked this conversation as resolved.
Comment thread
ben-alkov marked this conversation as resolved.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[medium] backward-incompatible

Making verified_variables and unchecked_variables required on the finding definition forces all finding producers across all review dimensions to emit empty arrays for fields semantically meaningful only for the security sub-agent. Consider making these fields optional or conditionally required for security-related categories only.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

We made the conscious decision for them to be always required

Comment thread
ben-alkov marked this conversation as resolved.
Comment thread
ben-alkov marked this conversation as resolved.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[medium] breaking-schema

Adding verified_variables and unchecked_variables to the required array is a backward-incompatible schema change. The companion fullsend PR (#2363) was CLOSED, not merged. AGENTS.md section 6 documents lockstep versioning between repos.

Suggested fix: Open a new companion PR in fullsend-ai/fullsend that updates the Finding Go struct in internal/cli/postreview.go, and link it from this PR's description.

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] schema-compatibility

The new fields are required rather than optional. Any existing or external producer of findings without them will fail schema validation immediately. Adding as optional would have preserved backward compatibility while allowing producers to adopt at their own pace.

"properties": {
"severity": { "type": "string", "enum": ["critical", "high", "medium", "low", "info"] },
"category": { "type": "string", "minLength": 1 },
Expand All @@ -115,7 +115,17 @@
"remediation": { "type": "string" },
"actionable": {
"type": "boolean",
"description": "When true with a non-empty remediation, routes the verdict to request-changes so the fix agent can address the finding automatically."
"description": "True when this non-blocking finding should be tracked as a follow-up issue if the review approves."

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] scope-creep

The PR changes the actionable field's schema description from 'When true with a non-empty remediation, routes the verdict to request-changes...' to 'True when this non-blocking finding should be tracked as a follow-up issue if the review approves.' This semantic change is not authorized by issue #2095.

},
"verified_variables": {
Comment thread
ben-alkov marked this conversation as resolved.
"type": "array",
"items": { "type": "string", "minLength": 1 },
"description": "Variables in the security-sensitive context that were verified as having the security control applied."
},
"unchecked_variables": {
"type": "array",
"items": { "type": "string", "minLength": 1 },
"description": "Variables in the security-sensitive context that were not verified as having the security control applied."
}
},
"additionalProperties": false
Expand Down
Loading
Loading