mirror of
https://github.com/alibaba/open-code-review.git
synced 2026-08-31 02:07:08 +00:00
9 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
980f21d6f2
|
chore: remove leftover Chinese from the Go core, CI examples and pages comments (#861)
* docs(comments): translate Chinese comments to English
Rewrite the remaining Chinese code comments outside the VSCode extension
in English, so the Go core and the pages site read consistently.
- allowed_ext.go: translate the default_exclude_patterns.json package doc.
Quote the wildcards ("*", "**", "{a,b,c}") so gofmt stops reflowing the
leading "*" as a markdown bullet, which had swallowed the first entry's
wildcard and broken the list alignment in godoc.
- HighlightsSection.tsx: translate three comments in parseStatValue and
CountUpValue.
* docs(examples): use "Chinese" instead of "中文" in OCR_LANGUAGE examples
The language config value is fed to the LLM, which understands "Chinese"
just as well, and "Chinese" is what the rest of the project already uses
(skills/open-code-review/SKILL.md, config_cmd_test.go, ApplyLanguage).
Keeps the GitLab CI example's inline docs fully English.
* fix(agent): drop the unreachable Chinese branch from planBlockPattern
task_template.json ships a single English template ("### Review Plan
(Optional)") and is embedded via go:embed with no override path, so the
"审查计划" alternative could never match anything. It came from the
pre-open-source template and survived the #33 fix as dead defensive code.
Drops the two test cases that only exercised that alternative.
|
||
|
|
84be9d0413
|
feat(examples/gitlab): align GitLab CI review posting with the GitHub Action (#767)
* feat(gitlab-ci): align review posting with the GitHub Action behaviors
Port the production-hardening of scripts/github-actions/post-review-comments.js
into examples/gitlab_ci/post_review.py so the GitLab example matches the
GitHub Action's publication semantics.
Refactor the transport from a single post() callable into a Poster object
(GitLabPoster / DryRunPoster) exposing post_note / post_discussion / list_notes
/ update_note / list_discussions / get_mr_diffs, so reads (sticky upsert,
incremental history, idempotent reconciliation) and the 400-fallback diff
inventory can flow through it.
Pure-logic ports (Group A):
- category/severity badge [category · severity] on every inline discussion
and fallback entry (byte-matching the CLI's buildBadge)
- publication policy routing findings to the summary by severity
(OCR_ROUTE_SEVERITY_BELOW) and category (OCR_ROUTE_CATEGORIES), fail-open
on unknown metadata; routed findings never enter the inline write path
- deterministic sort (path -> start_line -> end_line -> index) before posting
- detailed warning rendering (file (type): message bullets)
- backtick-safe fenced code blocks in the fallback Before/After; inline
suggestion keeps the fixed suggestion:-0+0 fence so the Apply-suggestion
button keeps working
Read-API features (Group B):
- sticky summary (default ON): the summary note is updated in place across
runs via a <!-- ocr-summary --> marker (find-then-PUT), instead of
accumulating one note per run; a pre-review anchor pins it above the
discussions on the first run
- incremental mode (OCR_INCREMENTAL, default off): skip comments whose line
range overlaps a prior bot discussion on the same path, using IoU above
OCR_INCREMENTAL_OVERLAP_THRESHOLD (default 0.6); single-line vs multi-line
never match; bot discussions detected by the <!-- ocr- marker
- idempotent retry: each inline discussion carries an invisible
<!-- ocr-<pipeline>-<job>-<hex> --> id tag; on a 5xx/408/network failure
(maybe-reached-server) the script GETs /discussions and skips the retry
when the id is found (no duplicate); when the read API is unavailable it
skips the retry and surfaces the reason rather than risk a duplicate
C group (minus batched createReview):
- 400 line-resolution fallback: when a discussion POST returns 400 with a
position error, fetch GET /merge_requests/:iid/diffs, classify the comment
(valid/invalid/unknown), and drop provably-out-of-diff findings to the
summary instead of blindly retrying; unknown keeps the existing fallback
- wait-until-reset: when RateLimit-Remaining == 0, sleep until
RateLimit-Reset (defensively handling both epoch-seconds and
seconds-until-reset formats), ahead of Retry-After / backoff
D group (pipeline alignment):
- dotenv stats output (/tmp/ocr-stats.env) with OCR_COMMENTS_{TOTAL,INLINE,
SUMMARY,ROUTED,SKIPPED,FAILED} and OCR_SUMMARY_URL, exposed via a
reports: dotenv artifact
- artifacts (when: always, 1 week): /tmp/ocr-result.json and
/tmp/ocr-stderr.log so a failed review is still inspectable
- severity fail gating (OCR_FAIL_ON_SEVERITY): non-zero exit when a comment
severity is at/above the threshold; the summary note is still posted first
Tests: 57 -> 117 (stdlib unittest, no network, no wall-clock). New coverage
for badge/policy/sort/warnings/safe-fence, sticky upsert (cold start + reuse),
incremental IoU (single/multi/cross-type/read-failure), idempotent
reconciliation (posted/unavailable/not-posted/400-no-reconcile), 400
classification (valid/invalid/unknown/truncated patch), wait-until-reset
(seconds/epoch/non-zero-remaining), stats file, severity gating.
The only behavior change for existing users: the summary note now updates in
place by default (sticky_summary default true, matching the GitHub Action);
incremental/routing/gating default to off / no-op.
* fix(gitlab-ci): harden OCR_INCREMENTAL_OVERLAP_THRESHOLD parsing, drop dead regex
- build_config used a bare float() on OCR_INCREMENTAL_OVERLAP_THRESHOLD,
which raised an unhandled ValueError and crashed the script when a user
set a non-numeric value. Reuse resolve_threshold, which already handles
non-numeric / out-of-range values and falls back to the default.
- Remove the unused _COMMENT_ID_RE module-level regex; the idempotency
check in _is_comment_posted uses a plain substring match, so the regex
was dead code.
Tests: +2 covering non-numeric and out-of-range env values (117 -> 119).
* fix(gitlab-ci): strip control chars in route_comment, drop dead badge init
- route_comment read category/severity via str(...).strip().lower() without
stripping C0/C1 control characters, while build_badge uses sanitize_metadata
for the same fields. If the LLM emits embedded control chars (e.g. "bug\r"),
the badge rendered [bug · high] but routing classified it as "unknown" and
skipped policy routing. Reuse sanitize_metadata to stay consistent.
- format_comment_fallback had a duplicate md/badge initialization block
immediately overwritten by an identical block, a merge artifact. Removed
the redundant first block.
* fix(gitlab-ci): classify line_code 400s, match MR version to OCR-reviewed SHAs
- LINE_RESOLUTION_PATTERNS missed the `line_code` substring, so GitLab's
`:line_code=>["can't be blank", "must be a valid line code"]` 400 was never
classified as a line-resolution failure. The per-comment diff-inventory
fallback never fired, and the raw error JSON leaked into the summary note
as `⚠️ Could not be posted inline: {"message":"400 Bad request - Note ..."}`
instead of a friendly "out of diff" / "line resolution failure" reason. Add
`line_code` to the pattern list.
- fetch_diff_refs took `versions[0]` blindly. After a force-push or follow-up
commit, that version's head_commit_sha no longer matches the diff OCR
actually reviewed, so the position's base_sha/head_sha describe the wrong
diff and every inline position fails to resolve on GitLab's side — the root
cause of the line_code 400 above. Now read resolved_head/resolved_base from
the OCR result manifest and pick the matching MR version, falling back to
newest-by-created_at with a warning when no version matches.
- Migrate artifact paths /tmp -> .ocr (GitLab Runner refuses to upload
artifacts outside the build directory) and drop the now-redundant temporary
sticky-summary diagnostic block.
- Add 7 tests: line_code pattern positive, version matching by head /
head+base / no-match-fallback / no-expected-shas-sorts-newest, and
_extract_expected_shas with/without manifest.
* feat(gitlab-ci): close GitHub Action parity gaps (config, flags, exit gate, pacing, per-run summary)
Brings the GitLab CI example to feature parity with the GitHub Action
(action.yml + scripts/github-actions/post-review-comments.js).
- .gitlab-ci.yml: expose action.yml inputs as CI/CD variables.
- OCR_VERSION pins the npm spec (default latest) for reproducible reviews.
- Optional LLM config written via `ocr config set` only when the CI var is
non-empty (no clobbering): OCR_LANGUAGE, OCR_LLM_AUTH_HEADER,
OCR_LLM_EXTRA_HEADERS. OCR_LLM_TIMEOUT is read natively by OCR from env.
- Review flags threaded env→flag: OCR_REVIEW_CONCURRENCY, OCR_BACKGROUND,
OCR_RULE.
- OCR exit-code gate: drop `|| true`, capture OCR_EXIT_CODE, run
post_review.py unconditionally (so stderr still posts to MR), then exit
non-zero on OCR failure — mirrors the Action's "Fail job on OCR error".
- Document `llm.use_anthropic` as a hardcoded (not CI-configurable) value
under a separate "Not a CI/CD variable" header; the false listing as a
required CI var was misleading.
- post_review.py: posting-logic parity.
- Read-API pacing: `_read_with_pacing` wraps list_notes/list_discussions/
get_mr_diffs with read_success_delay (0.5s default) and
read_low_remaining_spacing (5s default) when RateLimit-Remaining is
at/below rate_limit_threshold — mirrors readWithPacing so a large MR
does not hammer the read API.
- Non-sticky per-run summary matching: `summary_tag_for(run_tag)` /
`wrap_summary_body` embed both SUMMARY_MARKER and a per-run
`<!-- ocr-summary-run:TAG -->`; find_summary_note/upsert_summary/
ensure_summary_anchor/finalize_summary take a `tag` param. The pre-review
anchor now runs in non-sticky mode too (was sticky-only), so non-sticky
shows the "⏳ Posting…" live state and finalize updates the same note
instead of creating a duplicate.
- build_config exposes read_success_delay / read_low_remaining_spacing.
- tests (+12, 142 total, 0.01s):
- ReadPacingTest: success-delay pacing, low-quota long spacing, no pacing
sleep on failed read.
- NonStickySummaryTest: cold-start anchor→update same note; same-run reuse;
different-run not reused.
- SummaryTagPureTest: tag format, wrap embeds both markers, newest-first
find by tag/marker, None on no match.
- BuildConfigTest: read-pacing defaults & overrides.
- Recorder.final_summary_body / summary_call_count helpers adapt existing
assertions to the two-phase anchor→finalize flow.
- MainAuthHeaderTest: mock urlopen with canned responses — was making real
DNS lookups to gitlab.example, amplified 3x by the new always-on anchor
(13.7s → 0.01s).
- README.md: document the 9 new CI vars in the variables table, add
read-pacing vars to the retry/delay table, convert the version/rules/
concurrency/background recipes from "edit the YAML" to "set this CI var".
* fix(gitlab-ci): if-guard optional vars, capture post_review exit code, split diff-refs reason
- Replace [ -n "$VAR" ] && cmd with if-guards so FF_ENABLE_BASH_EXIT_CODE_CHECK
cannot abort on an empty optional variable (|| true would mask real failures)
- Capture post_review.py exit code (POST_EXIT_CODE) so the OCR_EXIT_CODE gate
runs regardless of severity-gate or posting failures
- Split 'not path or not end_line or not diff_refs' into two checks so
diff-refs-unavailable comments get an accurate reason in the summary
* fix(gitlab-ci): default OCR/POST exit codes to guard against unset gate vars
Without a default, an unset OCR_EXIT_CODE/POST_EXIT_CODE makes the gate
`[ "" != "0" ]` evaluate true and `exit ""` fail with
'numeric argument required'. Default both to 0 so the gate is always safe.
|
||
|
|
e75601d2e6
|
feat(examples/gitlab): add fail-open category/severity publication controls (#685)
* feat(examples/gitlab): add fail-open category/severity publication controls Port the GitHub Action publication policy (#478/#529) to the GitLab CI example so MRs can badge findings and optionally route low-signal categories/severities to summary notes without dropping them. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(examples/gitlab): sanitize metadata in route_comment Align routing with build_badge so control characters in LLM category/severity values do not desync badge labels from policy matching. --------- Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
e670b3b3f6
|
refactor(examples): extract GitLab CI heredoc into testable post_review.py with unit tests (#539)
Some checks are pending
CI / test (push) Waiting to run
CI / cross-compile (amd64, darwin) (push) Waiting to run
CI / cross-compile (amd64, windows) (push) Waiting to run
CI / cross-compile (arm64, darwin) (push) Waiting to run
CI / cross-compile (arm64, linux) (push) Waiting to run
CI / cross-compile (arm64, windows) (push) Waiting to run
* refactor(examples): extract GitLab CI heredoc into post_review.py with unit tests Extracts the ~270-line inline heredoc from .gitlab-ci.yml into a standalone, testable post_review.py module, matching the publish() + make_poster() pattern established by gerrit_ci/ and gitflic_ci/. Key design decisions (from spec issue #1 and wayfinder tickets #3, #4, #5): - publish(result, diff_refs, post, config, sleep) — transport-agnostic - make_poster(api_base, token, auth_header, config) — GitLab REST transport - fetch_diff_refs(api_base, token, auth_header, config) — /versions GET with retry - Single config dict built by main() from env vars; no module-level config state - post() returns {success, rate_limit_remaining, is_rate_limit_exhausted} to preserve failure-pacing behavior (rate-limit vs non-rate-limit delays) - _sleep = time.sleep module-level pattern for testability All existing heredoc behavior preserved 1:1: - GitLab suggestion:-0+0 syntax and <details> fallback format - Retry on 429/403-rate-limit/5xx/408 with exponential backoff + ±25% jitter - Retry-After header honoring, MAX_RETRY_DELAY cap - Proactive RateLimit-Remaining throttling (success path only) - Failure pacing: rate-limit-exhausted → SUCCESS_DELAY, other → FAILURE_DELAY - PRIVATE-TOKEN vs JOB-TOKEN auth selection - Inline → fallback → summary ordering - Parse failure → post stderr as error note - All 6 env vars (OCR_RETRY_BASE_DELAY, OCR_MAX_RETRIES, OCR_MAX_RETRY_DELAY, OCR_SUCCESS_DELAY, OCR_FAILURE_DELAY, OCR_RATE_LIMIT_THRESHOLD) 48 unit tests (stdlib unittest, no network, no real time.sleep): - Seam 1: publish() with Recorder fake poster — inline/fallback/summary flow, proactive throttling, failure pacing - Seam 2: make_poster() with mocked urlopen + _sleep — retry/backoff/jitter, Retry-After, delay cap, auth headers, is_rate_limit_exhausted classification - fetch_diff_refs() with mocked urlopen — success/failure/retry - build_config() defaults and env overrides - Dry-run poster — no HTTP calls Implements #534. * fix(examples): address code review findings on gitlab_ci post_review - Add missing-required check for CI_PROJECT_ID and CI_MERGE_REQUEST_IID in main(), matching gerrit_ci/gitflic_ci pattern. The heredoc used os.environ[...] (KeyError on missing); the extraction silently used env.get(..., "") which constructs a malformed API URL. Now fails fast with a clear error message. - Change transient_base_delay from int 2 to float 2.0 to match spec config-dict type annotation. - Add 5 end-to-end tests for main()'s auth-header env resolution: PRIVATE-TOKEN when GITLAB_API_TOKEN set, JOB-TOKEN when only CI_JOB_TOKEN set, PRIVATE-TOKEN wins when both set, missing CI vars fails fast, missing token fails fast. Addresses review findings: #3 (TP, medium), #4 (Edge), #5 (TP, low). * fix(examples): handle URLError in gitlab_ci post_review retry logic _api_request_with_retry only caught HTTPError, not URLError. Network-layer failures (DNS resolution failure, connection refused, connection reset) raised URLError which propagated uncaught, crashing the script and losing all pending review comments. The original heredoc had the same gap, but the gerrit_ci sibling already handles this correctly (lines 250-259: retry on connection errors, propagate timeouts). This fix follows the gerrit_ci pattern adapted to our return-dict contract: - Add 'except urllib.error.URLError' handler after HTTPError handler - Timeout (socket.timeout/TimeoutError): return failure dict, don't retry (ambiguous — server may have processed the request) - Connection errors (DNS, refused, reset): retry with transient_base_delay backoff + ±25% jitter, same as 5xx/408 handling - Exhaustion: return failure dict with is_rate_limit_exhausted=False 3 new tests: - test_retry_urlerror_then_success: ConnectionRefused → retry → success - test_urlerror_exhausts_retries: 4 ConnectionRefused → failure after 4 attempts - test_urlerror_timeout_not_retried: socket.timeout → immediate failure, no retry Found by OCR (open-code-review) AI code review. * fix(examples): handle non-UTF-8 HTTP error bodies in gitlab_ci post_review e.read().decode('utf-8') raises UnicodeDecodeError when the GitLab server returns a non-UTF-8 error body (e.g., an HTML error page in latin-1 from a misconfigured proxy or load balancer). This exception propagated uncaught, crashing the entire posting loop — no further inline comments, fallback notes, or summary notes would be posted. Both gerrt_ci (line 242: decode('utf-8', 'replace')) and gitflic_ci (line 344: decode('utf-8', 'replace')) siblings already handle this correctly. The original heredoc had the same gap. Fix: add errors='replace' to both decode() calls (success path line 248 + error path line 260). For valid UTF-8 input (the normal case), behavior is identical. The error body is only used for keyword matching and logging, both of which work fine with replacement characters (U+FFFD). 1 new test: - test_non_utf8_error_body_does_not_crash: HTTPError with invalid UTF-8 body → no crash, returns failure dict Found by OCR (open-code-review) AI code review on PR #539. |
||
|
|
83dacc2725
|
docs(examples): OCR_LLM_MODEL is required, not optional (#431) | ||
|
|
7f22ba867d
|
fix: actions rate limit (#164)
* ci: add rate limit handling and version verification to GitHub Actions workflow - Add version check after OCR installation to verify successful setup - Implement exponential backoff retry logic for GitHub API rate limits - Add delays between individual comment posts to avoid secondary rate limits - GitHub enforces ~80 content-generating requests per minute; spacing calls helps stay under that threshold with 2-second base delay and up to 3 retries * ci: refine rate limit handling in GitHub Actions workflow - Add `|| true` to ocr version check for error isolation - Narrow rate limit detection to 429 and 403 with rate-limit message matching, avoiding retries on permission/auth failures - Extract hardcoded delay constants into env-configurable variables (OCR_RETRY_BASE_DELAY, OCR_MAX_RETRIES, OCR_SUCCESS_DELAY, OCR_FAILURE_DELAY) with sensible defaults - Document optional environment variables in workflow header * docs: add environment variable configuration guide for retry and delay settings * ci: add rate-limit resilience and version check to GitLab CI pipeline - Add `ocr version || true` after install for diagnostic logging - Add `api_request_with_retry` function with exponential backoff for 429 and 403 (rate-limit message matching) errors - Respect GitLab `Retry-After` header when present - Extract delay constants into CI/CD-configurable variables (OCR_RETRY_BASE_DELAY, OCR_MAX_RETRIES, OCR_SUCCESS_DELAY, OCR_FAILURE_DELAY) with defaults - Add pacing delays between successful/failed discussion posts - Document optional CI/CD variables in pipeline header comments * docs: add retry/delay settings section to GitLab CI README * ci: fix rate-limit retry delay exhaustion handling in CI workflows - GitHub Actions: distinguish exhausted rate-limit retries from other errors, apply SUCCESS_DELAY (2s) instead of FAILURE_DELAY (1s) when retries exhausted - GitLab CI: return structured result from api_request_with_retry to differentiate failure types, apply context-aware delays based on rate-limit exhaustion status - Both: prevent perpetuating rate-limit failures by using longer delays after retry exhaustion * ci: align GitHub Actions rate-limit retry with header-based strategy Derive wait durations from response headers (retry-after, x-ratelimit-reset) instead of fixed exponential backoff, add proactive throttle when remaining quota is low, honor batch-level rate limits before per-comment retry, and add support for transient 5xx/408 errors. * feat(gitlab-ci): enhance rate-limit handling with jitter, max retry delay, and proactive throttling - Add ±25% jitter on retry delays to avoid thundering herd problems - Add OCR_MAX_RETRY_DELAY (default 60s) to cap per-retry wait time - Add OCR_RATE_LIMIT_THRESHOLD (default 10) for proactive throttling based on GitLab RateLimit-Remaining response header - Parse Retry-After header properly (handle non-numeric values) - Apply retry logic to all API requests (notes, versions, discussions) - Parse and log RateLimit-Remaining/Limit headers for observability - Double pacing delay when remaining quota drops below threshold - Update README with new configuration variables and behavior docs * fix(examples): sync rate-limit docs with script defaults and add missing variables - GitHub Actions README: fix OCR_RETRY_BASE_DELAY default from 2000 to 60000 (matching script code and header comments) - GitHub Actions README: add missing OCR_RETRY_MAX_DELAY, OCR_LOW_REMAINING_THRESHOLD, OCR_LOW_REMAINING_SPACING variables - GitHub Actions README: add GitHub Rate Limits doc reference link - GitHub Actions yml header: add OCR_LLM_USE_ANTHROPIC and llm.extra_body notes - GitLab CI yml header: add llm.extra_body note - GitLab CI README: add GitLab Rate Limits doc reference link * ci(examples): unify header lookup and add transient retry backoff - GitHub Actions: extract inline header closure into a reusable getHeader helper; use it in both computeRetryDelayMs and logRateLimitQuota for consistent case-insensitive header access. - GitHub Actions: use a 2s transientBase for 5xx/408 exponential backoff instead of the 60s rate-limit base, since server hiccups are typically short-lived and the longer base stalled CI jobs unnecessarily. - GitLab CI: add a _get_header helper and route all header access (Retry-After, RateLimit-*) through it, matching the GitHub Actions approach. - GitLab CI: add transient retry logic for 5xx/408 errors with a 2s base delay, so server errors no longer fail immediately. |
||
|
|
a32b8c7e30
|
docs(examples): add concurrency control to CI workflow examples (#60)
* docs(examples): add concurrency control to CI workflow examples - GitHub Actions: add concurrency group with cancel-in-progress to avoid redundant review runs on rapid pushes - GitLab CI: add interruptible and resource_group to cancel outdated review jobs when new commits are pushed to the same MR * docs(examples): improve GitLab CI example with fork MR and concurrency support - Support forked MR pipelines by using CI_COMMIT_SHA as --to target - Fall back to CI_JOB_TOKEN when GITLAB_API_TOKEN is unavailable - Use appropriate auth header (JOB-TOKEN vs PRIVATE-TOKEN) based on token source - Add --audience agent flag for machine-consumable review output - Make diff_refs required for inline comments, simplify post_discussion signature - Improve summary with inline vs fallback comment breakdown - Add documentation comments for fork MR setup requirements * docs(examples): use pull_request_target and SHA refs for fork PR support - Switch trigger from pull_request to pull_request_target so secrets are available for PRs from forks - Use head SHA instead of branch ref for checkout and ocr --to, since fork branches don't exist on the origin remote - Add explicit fetch step to ensure fork commits are available - Update condition checks and comments to reflect the new event name * docs: sync READMEs with CI script changes for fork PR/MR support |
||
|
|
55c6bca1f8
|
chore: update ci pipeline examples (#45)
* chore: remove --audience agent flag and simplify JSON parsing in CI examples - Remove --audience agent flag from ocr review commands in CI examples - Simplify JSON output parsing by reading directly without skipping first line - Update README docs to reflect the simplified CLI usage * fix: correct typo in .gitlab-ci.yml comment Change 'confuring' to 'setting' in CI variable configuration comment. |
||
|
|
128787b627
|
Add CI/CD integration section and examples to documentation (#11)
* docs: add CI/CD integration section and examples - Add CI/CD Integration section to README.md and README.zh-CN.md - Add GitHub Actions workflow example (examples/github_actions/) - Add GitLab CI pipeline example (examples/gitlab_ci/) - Add examples README with overview of integration options * feat(examples): enhance GitHub Actions demo with comment trigger and improved error handling - Add issue_comment event trigger with /open-code-review and @open-code-review keywords - Add PR context resolution for comment-triggered events via GitHub API - Improve ref handling to support both PR events and comment events - Add individual comment fallback with retry when batch review fails - Add posting statistics (success/failed counts) to summary comment - Update README with comment trigger flow and customization guide * docs(examples): add --background flag usage guide for GitHub Actions and GitLab CI Explain how to pass PR/MR title as background context to help OCR provide more relevant and context-aware review comments. * feat(examples): simplify PR trigger and add skip-existing-review guide for GitLab CI - Reduce GitHub Actions PR trigger to 'opened' only (avoid redundant reviews on synchronize/reopened events) - Add GitLab CI documentation for checking existing OCR comments before running review to save LLM tokens |