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
101 changes: 66 additions & 35 deletions .github/workflows/opencode-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3271,13 +3271,13 @@ jobs:
OPENCODE_SMALL_CHANGE_FILE_THRESHOLD: "3"
OPENCODE_MEDIUM_CHANGE_FILE_THRESHOLD: "20"
OPENCODE_SMALL_CHANGE_RUN_TIMEOUT_SECONDS: "300"
OPENCODE_SMALL_CHANGE_TOTAL_BUDGET_SECONDS: "900"
OPENCODE_MEDIUM_CHANGE_RUN_TIMEOUT_SECONDS: "600"
OPENCODE_MEDIUM_CHANGE_TOTAL_BUDGET_SECONDS: "1500"
OPENCODE_LARGE_CHANGE_RUN_TIMEOUT_SECONDS: "900"
OPENCODE_LARGE_CHANGE_TOTAL_BUDGET_SECONDS: "1500"
OPENCODE_UNKNOWN_CHANGE_RUN_TIMEOUT_SECONDS: "600"
OPENCODE_UNKNOWN_CHANGE_TOTAL_BUDGET_SECONDS: "1500"
OPENCODE_SMALL_CHANGE_TOTAL_BUDGET_SECONDS: "600"
OPENCODE_MEDIUM_CHANGE_RUN_TIMEOUT_SECONDS: "420"
OPENCODE_MEDIUM_CHANGE_TOTAL_BUDGET_SECONDS: "900"
OPENCODE_LARGE_CHANGE_RUN_TIMEOUT_SECONDS: "420"
OPENCODE_LARGE_CHANGE_TOTAL_BUDGET_SECONDS: "1080"
OPENCODE_UNKNOWN_CHANGE_RUN_TIMEOUT_SECONDS: "420"
OPENCODE_UNKNOWN_CHANGE_TOTAL_BUDGET_SECONDS: "900"
OPENCODE_DYNAMIC_MAX_CYCLES: "1"
CENTRAL_REVIEW_PROCESS_FALLBACK_ELIGIBLE: ${{ steps.central_review_process_fallback_scope.outputs.eligible || 'false' }}
CENTRAL_REVIEW_PROCESS_FALLBACK_SCOPE_LABEL: ${{ steps.central_review_process_fallback_scope.outputs.scope_label || 'unsupported' }}
Expand All @@ -3290,6 +3290,7 @@ jobs:
OPENCODE_AGENT: ci-review-fallback
OPENCODE_EVIDENCE_FILE: ${{ runner.temp }}/opencode-review-evidence.md
OPENCODE_CHANGED_FILES_FILE: ${{ runner.temp }}/opencode-changed-files.txt
OPENCODE_REQUIRE_ADVERSARIAL_VALIDATION: "true"
OPENCODE_OUTPUT_FILE: ${{ runner.temp }}/opencode-review-model-pool.md
OPENCODE_REVIEW_WORKDIR: ${{ runner.temp }}/opencode-review-project
OPENCODE_SOURCE_WORKDIR: ${{ runner.temp }}/opencode-pr-head
Expand Down Expand Up @@ -3397,6 +3398,8 @@ jobs:
# re-rejects it (NO_CONCLUSION / exit 4), failing an otherwise valid
# review instead of publishing it.
OPENCODE_EVIDENCE_FILE: ${{ runner.temp }}/opencode-review-evidence.md
OPENCODE_CHANGED_FILES_FILE: ${{ runner.temp }}/opencode-changed-files.txt
OPENCODE_REQUIRE_ADVERSARIAL_VALIDATION: "true"
# The publish gate re-runs source-backed validation against PR-head data.
OPENCODE_SOURCE_WORKDIR: ${{ runner.temp }}/opencode-pr-head
PR_BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.inputs.pr_base_sha }}
Expand Down Expand Up @@ -3666,6 +3669,13 @@ jobs:
HEAD_REF: ${{ github.event.pull_request.head.ref || github.event.inputs.pr_head_ref || '' }}
RUN_ID: ${{ github.run_id }}
RUN_ATTEMPT: ${{ github.run_attempt }}
OPENCODE_MODEL_POOL_OUTPUT_FILE: ${{ runner.temp }}/opencode-review-model-pool.md
OPENCODE_EVIDENCE_FILE: ${{ runner.temp }}/opencode-review-evidence.md
OPENCODE_CHANGED_FILES_FILE: ${{ runner.temp }}/opencode-changed-files.txt
OPENCODE_SOURCE_WORKDIR: ${{ runner.temp }}/opencode-pr-head
OPENCODE_REQUIRE_ADVERSARIAL_VALIDATION: "true"
PR_BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.inputs.pr_base_sha }}
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha || github.event.inputs.pr_head_sha }}
CENTRAL_REVIEW_PROCESS_FALLBACK_CHANGED_COUNT: ${{ steps.central_review_process_fallback_scope.outputs.changed_count || '0' }}
CENTRAL_REVIEW_PROCESS_FALLBACK_SCOPE_LABEL: ${{ steps.central_review_process_fallback_scope.outputs.scope_label || 'unsupported' }}
APPROVAL_CHECK_WAIT_ATTEMPTS: "12"
Expand All @@ -3683,6 +3693,27 @@ jobs:
exit 1
fi

model_output_copy="$(mktemp)"
normalized_control_file="$(mktemp)"
if [ ! -s "${OPENCODE_MODEL_POOL_OUTPUT_FILE:-}" ]; then
echo "::error::CENTRAL_FAST_APPROVAL_NO_MODEL_OUTPUT: selected current-head model output is unavailable."
exit 1
fi
perl -pe 's/\x1b\[[0-9;?]*[A-Za-z]//g' "$OPENCODE_MODEL_POOL_OUTPUT_FILE" >"$model_output_copy"
if ! python3 scripts/ci/opencode_review_normalize_output.py \
"$HEAD_SHA" "$RUN_ID" "$RUN_ATTEMPT" "$model_output_copy"; then
echo "::error::CENTRAL_FAST_APPROVAL_ADVERSARIAL_INVALID: selected model output did not satisfy the structured adversarial contract."
exit 1
fi
gate_result="$(
bash scripts/ci/opencode_review_approve_gate.sh \
"$HEAD_SHA" "$RUN_ID" "$RUN_ATTEMPT" "$model_output_copy" "$normalized_control_file"
)"
if [ "$gate_result" != "APPROVE" ]; then
echo "::notice::Central fast approval skipped because the adversarially validated model verdict was ${gate_result:-unknown}, not APPROVE."
exit 0
fi

api_url="https://api.github.com"
api_timeout="${REVIEW_PUBLISH_GH_API_TIMEOUT_SECONDS:-15}"
read_token="${CHECK_LOOKUP_GH_TOKEN:-$GH_TOKEN}"
Expand Down Expand Up @@ -3800,19 +3831,28 @@ jobs:
exit 1
fi

model_reason="$(jq -r '.reason' "$normalized_control_file")"
model_summary="$(jq -r '.summary' "$normalized_control_file")"
adversarial_evidence="$(jq -c '.adversarial_validation' "$normalized_control_file")"
body="$(printf '%s\n' \
"## Pull request overview" \
"" \
"OpenCode reviewed the current-head central review-process repair evidence and found no blocking issues." \
"$model_summary" \
"" \
"## Findings" \
"" \
"No blocking findings." \
"" \
"## Adversarial validation" \
"" \
'```json' \
"$adversarial_evidence" \
'```' \
"" \
"## Evidence" \
"" \
"- Result: APPROVE" \
"- Reason: central review-process fast approval; model-pool output succeeded and current-head peer checks, code-scanning alerts, and review threads were clear." \
"- Reason: ${model_reason}" \
"- Scope: \`${CENTRAL_REVIEW_PROCESS_FALLBACK_SCOPE_LABEL:-unknown}\`" \
"- Changed files: \`${CENTRAL_REVIEW_PROCESS_FALLBACK_CHANGED_COUNT:-unknown}\`" \
"- Head SHA: \`${HEAD_SHA}\`" \
Expand Down Expand Up @@ -3850,6 +3890,8 @@ jobs:
OPENCODE_EVIDENCE_FILE: ${{ runner.temp }}/opencode-review-evidence.md
OPENCODE_FAILED_CHECK_EVIDENCE_FILE: ${{ runner.temp }}/opencode-failed-check-evidence.md
OPENCODE_FAILED_CHECK_DIAGNOSIS_FILE: ${{ runner.temp }}/opencode-failed-check-diagnosis.md
OPENCODE_CHANGED_FILES_FILE: ${{ runner.temp }}/opencode-changed-files.txt
OPENCODE_REQUIRE_ADVERSARIAL_VALIDATION: "true"
COVERAGE_EVIDENCE_RESULT: ${{ needs.coverage-evidence.result || 'skipped' }}
COVERAGE_EVIDENCE_SUMMARY: ${{ needs.coverage-evidence.outputs.coverage_summary || 'Coverage evidence job did not run or did not publish coverage evidence.' }}
OPENCODE_REVIEW_WORKDIR: ${{ runner.temp }}/opencode-review-project
Expand Down Expand Up @@ -4654,9 +4696,11 @@ jobs:
local summary
local reason
local findings
local adversarial_evidence

summary="$(jq -r '.summary // ""' "$control_json")"
reason="$(jq -r '.reason // ""' "$control_json")"
adversarial_evidence="$(jq -c '.adversarial_validation' "$control_json")"
findings="$(
# shellcheck disable=SC2016
jq -r '
Expand Down Expand Up @@ -4684,6 +4728,8 @@ jobs:
printf '%s\n\n' "$findings"
printf '## Summary\n\n'
printf '%s\n\n' "$summary"
printf '## Adversarial validation\n\n'
printf '```json\n%s\n```\n\n' "$adversarial_evidence"
printf -- '- Result: REQUEST_CHANGES\n'
printf -- '- Reason: %s\n\n' "$reason"
printf -- "- Head SHA: \`%s\`\n" "$HEAD_SHA"
Expand Down Expand Up @@ -6049,7 +6095,7 @@ jobs:
' >"$output_file"
}

approve_current_head_after_model_unavailable() {
publish_blockers_after_model_unavailable() {
local pending_wait_status body

if [ "${COVERAGE_EVIDENCE_RESULT:-skipped}" != "success" ]; then
Expand Down Expand Up @@ -6136,30 +6182,8 @@ jobs:
return 0
fi

body="$(printf '%s\n' \
"## Pull request overview" \
"" \
"OpenCode model providers were unavailable for this same-head run, but deterministic current-head evidence is clean: coverage evidence passed, peer GitHub Checks are complete, medium-or-higher code-scanning alerts are clear, mergeability is clean, and reviewer threads are resolved or outdated." \
"" \
"## Findings" \
"" \
"No blocking findings." \
"" \
"## Evidence" \
"" \
"- Result: APPROVE" \
"- Reason: current-head model-unavailable evidence fallback; coverage, docstring, peer GitHub Checks, code-scanning alerts, mergeability, and review threads were clear for current head." \
"- Scope: \`${CENTRAL_REVIEW_PROCESS_FALLBACK_SCOPE_LABEL:-unknown}\`" \
"- Changed files: \`${CENTRAL_REVIEW_PROCESS_FALLBACK_CHANGED_COUNT:-unknown}\`" \
"- Model-pool outcome: \`${OPENCODE_MODEL_POOL_OUTCOME:-unknown}\`" \
"- Head SHA: \`${HEAD_SHA}\`" \
"- Workflow run: ${RUN_ID}" \
"- Workflow attempt: ${RUN_ATTEMPT}" \
"" \
"This fallback does not suppress failed checks, medium-or-higher code-scanning alerts, merge conflicts, unresolved reviewer threads, or failed coverage evidence; any of those conditions still publish REQUEST_CHANGES or leave the approval state unchanged."
)"
create_pull_review "APPROVE" "$body"
return 0
printf '::notice::MODEL_OUTPUT_UNAVAILABLE: current-head checks and alerts are clean, but no APPROVE review will be published without mandatory structured adversarial probes for head %s.\n' "$HEAD_SHA"
return 1
}

request_changes_for_merge_conflict_if_present() {
Expand Down Expand Up @@ -6262,7 +6286,7 @@ jobs:
# diagnostics. Do not read model output or change PR review state unless
# the pool explicitly emitted a valid current-head control block.
if [ "$opencode_review_outcome" != "success" ]; then
if approve_current_head_after_model_unavailable; then
if publish_blockers_after_model_unavailable; then
echo "::endgroup::"
exit 0
fi
Expand Down Expand Up @@ -6482,6 +6506,7 @@ jobs:
fi
summary="$(jq -r '.summary' "$control_json")"
reason="$(jq -r '.reason' "$control_json")"
adversarial_evidence="$(jq -c '.adversarial_validation' "$control_json")"
body="$(printf '%s\n' \
"## Pull request overview" \
"" \
Expand All @@ -6495,6 +6520,12 @@ jobs:
"" \
"$summary" \
"" \
"## Adversarial validation" \
"" \
'```json' \
"$adversarial_evidence" \
'```' \
"" \
"- Result: APPROVE" \
"- Reason: ${reason}" \
"- Head SHA: \`${HEAD_SHA}\`" \
Expand Down
14 changes: 13 additions & 1 deletion ci-review-prompt.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,18 @@ false-positive dismissal for each plausible peer finding, request changes with
your own line-specific finding and verification direction.
When another review bot reports a plausible current-head static-analysis, linter, compiler, or accessibility defect, verify the claim independently before approving. For JSX/TSX and component templates, duplicate props such as repeated `aria-label`, repeated event handlers, or assignments overwritten later in the same element/object are blocking when they can mask the intended accessible name, event behavior, data binding, or runtime value. Do not approve by merely citing the peer bot; inspect the changed hunk or run the relevant parser/linter/typecheck in a scratch workspace, then either publish your own source-backed finding or explain the source-backed false-positive dismissal.

Perform an explicit adversarial phase before every verdict. Assume the patch is
wrong, derive concrete failure hypotheses for each materially changed surface,
and attempt to trigger them with malformed or boundary inputs, authorization or
tenant crossover, stale or concurrent state, dependency/runtime mismatch,
error/rollback behavior, numerical extremes, or mobile/accessibility behavior
as applicable. A green check or absence of a known bug is not a probe. Record
the exact changed path, positive line, counterexample, executed or source-backed
evidence, and whether the hypothesis was falsified or confirmed in the
`adversarial_validation` control field. APPROVE needs two falsified probes for
material code/workflow/config/package/test changes and one for non-code changes;
REQUEST_CHANGES needs a confirmed probe anchored to a published finding.

Review the diff first, then inspect surrounding code only when needed to
understand impact. Evaluate correctness, API compatibility, security/privacy,
data integrity, concurrency, error handling, observability, performance,
Expand Down Expand Up @@ -224,4 +236,4 @@ The final OpenCode output must still satisfy the existing
`opencode-review-control-v1` JSON contract required by the approval gate. Use
the reviewer rubric above for analysis and human-readable review quality, but
return the sentinel and control block exactly as requested by the workflow
prompt.
prompt, including the mandatory structured `adversarial_validation` evidence.
10 changes: 10 additions & 0 deletions code-reviewer-prompt.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,16 @@ verify that `prefers-reduced-motion: reduce` users are not forced through smooth
scrolling or animated motion.
Treat peer review bot comments as adversarial seeds, not authority. If a peer bot flags a plausible current-head static-analysis, compiler, linter, or accessibility issue, independently verify it from the source hunk, parser/linter/typecheck output, runtime/library documentation, or a scratch repro before approving. In JSX/TSX and component templates, duplicate props such as repeated `aria-label`, repeated handlers, or assignments overwritten later in the same element/object are material defects when they can mask the intended accessible name, event behavior, data binding, or runtime value; report your own source-backed finding instead of merely quoting the peer bot.

Run a dedicated adversarial phase before the verdict. Assume the proposed patch
is wrong and build concrete counterexamples for each material changed surface:
malformed or boundary inputs, authorization or tenant crossover, stale or
concurrent state, dependency/runtime mismatch, error and rollback behavior,
numerical extremes, or mobile and accessibility behavior as applicable. Trace
or execute each probe and record the exact changed path, positive line,
hypothesis, attack/counterexample, evidence, and falsified/confirmed outcome in
the workflow's structured `adversarial_validation` control field. Green checks
alone and absence of a known failure are not adversarial evidence.

Implementation completeness is mandatory. Inspect changed runtime code and
connected call sites for placeholder bodies such as `pass`, `...`,
`NotImplementedError`, TODO-only branches, fake or constant returns, and
Expand Down
1 change: 1 addition & 0 deletions scripts/ci/opencode_review_approve_gate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,7 @@ normalized = {
"result": control["result"],
"reason": control["reason"],
"summary": control["summary"],
"adversarial_validation": control.get("adversarial_validation"),
"findings": control.get("findings") or [],
}
Path(sys.argv[2]).write_text(
Expand Down
Loading
Loading