Commit Graph
82 Commits
Author SHA1 Message Date
Rodin 57e62a345f feat(persona): add role-based review personas
PR Ready Gate / clear-labels (pull_request) Successful in 2s
CI / test (pull_request) Successful in 9m31s
CI / review (/anthropic/v1, anthropic--claude-4.6-sonnet, sonnet, anthropic, SONNET_REVIEW_TOKEN) (pull_request) Successful in 10m3s
CI / review (/openai/v1, gpt-5, gpt, openai, GPT_REVIEW_TOKEN) (pull_request) Successful in 11m30s
CI / review (/openai/v1, gpt-5, security, openai, SECURITY_REVIEW.md, SECURITY_REVIEW_TOKEN) (pull_request) Successful in 10m56s
Add persona system for specialized review roles. Each persona defines:
- A specific review focus (security, architecture, documentation)
- Custom system prompt additions
- Personality/tone adjustments

Built-in personas: security, architect, docs
Custom personas: load from JSON via persona-file flag

Includes workspace validation to prevent path traversal attacks.

Closes #51
2026-05-10 09:14:48 -07:00
Rodin 44d6fa9d57 ci: always run ready gate on synchronize
CI / review (/openai/v1, gpt-5, gpt, openai, GPT_REVIEW_TOKEN) (push) Has been skipped
CI / review (/openai/v1, gpt-5, security, openai, SECURITY_REVIEW.md, SECURITY_REVIEW_TOKEN) (push) Has been skipped
CI / test (push) Successful in 13s
CI / review (/anthropic/v1, anthropic--claude-4.6-sonnet, sonnet, anthropic, SONNET_REVIEW_TOKEN) (push) Has been skipped
Remove conditional - just always try to clear the labels. The curl
commands handle missing labels gracefully with || true.
2026-05-10 08:47:36 -07:00
Rodin 4ea41e164e ci: add ready label to PR ready gate
CI / test (push) Successful in 14s
CI / review (/anthropic/v1, anthropic--claude-4.6-sonnet, sonnet, anthropic, SONNET_REVIEW_TOKEN) (push) Has been skipped
CI / review (/openai/v1, gpt-5, gpt, openai, GPT_REVIEW_TOKEN) (push) Has been skipped
CI / review (/openai/v1, gpt-5, security, openai, SECURITY_REVIEW.md, SECURITY_REVIEW_TOKEN) (push) Has been skipped
Also clear the ready label (ID 38) on push, matching gargoyle behavior.
2026-05-10 08:44:24 -07:00
Rodin b24c4dcc86 ci: add PR ready gate to clear self-reviewed label on push
CI / test (pull_request) Successful in 14s
CI / review (/openai/v1, gpt-5, gpt, openai, GPT_REVIEW_TOKEN) (pull_request) Successful in 1m10s
CI / review (/anthropic/v1, anthropic--claude-4.6-sonnet, sonnet, anthropic, SONNET_REVIEW_TOKEN) (pull_request) Successful in 26s
CI / review (/openai/v1, gpt-5, security, openai, SECURITY_REVIEW.md, SECURITY_REVIEW_TOKEN) (pull_request) Successful in 1m5s
When a PR is pushed after being marked self-reviewed, the label is now
stale and should be removed. This matches the gargoyle CI behavior.

On synchronize:
- Remove self-reviewed label if present
- Reassign PR back to the author
2026-05-10 08:39:19 -07:00
Rodin ced1fa7ffd ci: fix model names to match SAP AI Core deployments
CI / test (pull_request) Successful in 14s
CI / review (/anthropic/v1, anthropic--claude-4.6-sonnet, sonnet, anthropic, SONNET_REVIEW_TOKEN) (pull_request) Successful in 26s
CI / review (/openai/v1, gpt-5, security, openai, SECURITY_REVIEW.md, SECURITY_REVIEW_TOKEN) (pull_request) Successful in 35s
CI / review (/openai/v1, gpt-5, gpt, openai, GPT_REVIEW_TOKEN) (pull_request) Successful in 50s
- Restore sonnet reviewer with correct model name (anthropic--claude-4.6-sonnet)
- Remove gpt-4.1, gpt-4.1-mini, gpt-5-mini (not deployed on SAP AI Core)
- Keep gpt-5 and security reviewers

The previous model names (claude-sonnet-4-6, etc.) were incorrect —
SAP AI Core uses 'anthropic--claude-4.6-sonnet' format.
2026-05-10 08:23:10 -07:00
Rodin 6b615c77d5 ci: remove unavailable models from review matrix
CI / test (pull_request) Successful in 15s
CI / review (/openai/v1, gpt-5, security, openai, SECURITY_REVIEW.md, SECURITY_REVIEW_TOKEN) (pull_request) Successful in 38s
CI / review (/openai/v1, gpt-5, gpt, openai, GPT_REVIEW_TOKEN) (pull_request) Successful in 49s
Models claude-sonnet-4-6, gpt-4.1, gpt-4.1-mini, and gpt-5-mini are not
deployed on the LLM proxy, causing 502 errors. Keep only gpt-5 which
is the only available model.
2026-05-10 03:15:04 -07:00
Rodin f48288bf2e fix: address review feedback — tokens, secrets, no hardcoded IPs
CI / test (pull_request) Successful in 14s
CI / review (/anthropic/v1, claude-sonnet-4-6, sonnet, anthropic, SONNET_REVIEW_TOKEN) (pull_request) Successful in 22s
CI / review (/openai/v1, gpt-4.1-mini, gpt41-mini, openai, GPT_REVIEW_TOKEN) (pull_request) Successful in 21s
CI / review (/openai/v1, gpt-4.1, gpt41, openai, GPT_REVIEW_TOKEN) (pull_request) Successful in 22s
CI / review (/openai/v1, gpt-5, security, openai, SECURITY_REVIEW.md, SECURITY_REVIEW_TOKEN) (pull_request) Successful in 45s
CI / review (/openai/v1, gpt-5, gpt, openai, GPT_REVIEW_TOKEN) (pull_request) Successful in 52s
CI / review (/openai/v1, gpt-5-mini, gpt5-mini, openai, GPT_REVIEW_TOKEN) (pull_request) Successful in 48s
- Fix token_secret for gpt41/gpt5-mini/gpt41-mini: use GPT_REVIEW_TOKEN
  instead of SONNET_REVIEW_TOKEN (wrong reviewer identity)
- Move LLM base URL back to secrets.LLM_BASE_URL (prevents exfiltration
  via PR-controlled matrix values)
- Remove hardcoded internal IP from workflow file; only provider path
  suffix (/anthropic/v1, /openai/v1) remains in matrix

Addresses: security-review-bot REQUEST_CHANGES (major: exfiltration risk,
minor: HTTP/hardcoded IP) and sonnet-review-bot REQUEST_CHANGES (major:
wrong token_secret on gpt entries).
2026-05-03 08:42:08 -07:00
Rodin b4c994d0fa ci: fix reviewer models — sonnet uses Anthropic, gpt uses GPT-5
CI / test (pull_request) Successful in 14s
CI / review (http://100.86.77.84:6655/openai/v1, gpt-4.1-mini, gpt41-mini, openai, SONNET_REVIEW_TOKEN) (pull_request) Successful in 19s
CI / review (http://100.86.77.84:6655/openai/v1, gpt-4.1, gpt41, openai, SONNET_REVIEW_TOKEN) (pull_request) Successful in 22s
CI / review (http://100.86.77.84:6655/anthropic/v1, claude-sonnet-4-6, sonnet, anthropic, SONNET_REVIEW_TOKEN) (pull_request) Successful in 24s
CI / review (http://100.86.77.84:6655/openai/v1, gpt-5, gpt, openai, GPT_REVIEW_TOKEN) (pull_request) Successful in 1m14s
CI / review (http://100.86.77.84:6655/openai/v1, gpt-5, security, openai, SECURITY_REVIEW.md, SECURITY_REVIEW_TOKEN) (pull_request) Successful in 54s
CI / review (http://100.86.77.84:6655/openai/v1, gpt-5-mini, gpt5-mini, openai, SONNET_REVIEW_TOKEN) (pull_request) Successful in 55s
The matrix was wrong: "sonnet" was running GPT-5 and "gpt" was running
GPT-4.1. Now:
- sonnet → Claude Sonnet 4.6 via HAI Anthropic endpoint
- gpt → GPT-5 via HAI OpenAI endpoint
- security → GPT-5 via HAI OpenAI endpoint

Each matrix entry specifies its own provider and base_url.
2026-05-02 21:06:11 -07:00
Rodin a0fd882b0d fix: address review findings
CI / test (pull_request) Successful in 14s
CI / review (gpt-4.1, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 24s
CI / review (gpt-5, security, SECURITY_REVIEW.md, SECURITY_REVIEW_TOKEN) (pull_request) Successful in 37s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m4s
- Tighten timeline matching: also check ev.User.Login matches
  the review author (prevents collision on identical body prefix)
- Remove unused sharedTokenMode variable (inline condition)
- Aggregate resolution failures with warn-level summary
2026-05-02 13:31:59 -07:00
Rodin d4bf13eeab fix: supersede ALL old reviews, not just the most recent
CI / test (pull_request) Successful in 14s
CI / review (gpt-4.1, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 22s
CI / review (gpt-5, security, SECURITY_REVIEW.md, SECURITY_REVIEW_TOKEN) (pull_request) Successful in 46s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m7s
Previously findOwnReview returned only the single most-recent matching
review, so on PRs with multiple force-pushes only the latest old review
got superseded. The rest accumulated as unsuperseded stale reviews.

Changes:
- Add findAllOwnReviews() to collect all non-superseded matching reviews
- Loop over all old reviews in the supersede phase
- Add GetTimelineReviewCommentIDForReview() to find comment IDs by
  review ID (fetches review body, matches in timeline by prefix)
- Each old review gets independently superseded and its inline comments
  resolved

The old findOwnReview is kept for backward compat (tested, may be
useful as a utility).
2026-05-02 13:28:03 -07:00
Rodin bc5a4a1dcd feat: resolve old inline comments when superseding review
CI / test (pull_request) Successful in 14s
CI / review (gpt-4.1, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 22s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 44s
CI / review (gpt-5, security, SECURITY_REVIEW.md, SECURITY_REVIEW_TOKEN) (pull_request) Successful in 46s
Closes #27

After superseding an old review, resolves all its inline comments via
POST /pulls/comments/{id}/resolve. This clears unresolved conversation
markers from the PR timeline and diff view.

New API methods:
- ListReviewComments: paginated GET /repos/.../pulls/{n}/reviews/{id}/comments
- ResolveComment: POST /repos/.../pulls/comments/{id}/resolve

Behavior:
- Only resolves after successful supersede (gated on supersedeOK)
- Aggregates failures and logs at warn level
- Truncates error bodies to 256 bytes (security)
- Non-fatal: review still posts even if resolution fails
2026-05-02 12:15:52 -07:00
Rodin 2507ee22e7 fix: address review findings on RequestReviewer
CI / test (pull_request) Successful in 13s
CI / review (gpt-4.1, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 21s
CI / review (gpt-5, security, SECURITY_REVIEW.md, SECURITY_REVIEW_TOKEN) (pull_request) Successful in 35s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m3s
- Accept 204 No Content as success (idempotent operations)
- Truncate error response body to 256 bytes (prevent log leakage)
- Add unit tests for GetAuthenticatedUser and RequestReviewer
2026-05-02 12:09:25 -07:00
Rodin c39845ca03 feat: self-request as reviewer before posting
CI / test (pull_request) Successful in 13s
CI / review (gpt-4.1, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 21s
CI / review (gpt-5, security, SECURITY_REVIEW.md, SECURITY_REVIEW_TOKEN) (pull_request) Successful in 48s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 58s
Closes #35

Before posting a review, the bot:
1. Discovers its own Gitea login via GET /user
2. Calls POST /requested_reviewers to add itself

This ensures the bot appears in the required-reviewers list without
manual configuration on the repo. The call is idempotent (no-op if
already requested).

Both failures are non-fatal (warn + continue) — the review still posts
even if the self-request fails.
2026-05-02 12:04:55 -07:00
Rodin 50091941e1 fix: trim trailing slash from giteaURL when building review link
CI / test (pull_request) Successful in 14s
CI / review (gpt-4.1, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 20s
CI / review (gpt-5, security, SECURITY_REVIEW.md, SECURITY_REVIEW_TOKEN) (pull_request) Successful in 28s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 41s
Prevents double-slash in supersede URL if GITEA_URL ends with '/'.
Aligns with how gitea.NewClient already normalizes the base URL.
2026-05-02 11:49:24 -07:00
Rodin ed69d26e87 fix: post new review first, then supersede old with link
CI / test (pull_request) Successful in 14s
CI / review (gpt-4.1, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 22s
CI / review (gpt-5, security, SECURITY_REVIEW.md, SECURITY_REVIEW_TOKEN) (pull_request) Successful in 44s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m8s
Changes the order of operations:
1. POST new review (gets non-stale badge immediately)
2. PATCH old review with superseded message linking to the new one

This gives the superseded comment a clickable link to the current
review, making navigation between review iterations easy.

buildSupersededBody now accepts a newReviewURL parameter.
2026-05-02 11:43:53 -07:00
Rodin f6baa41b2c fix: remove findOwnReviewStrict, use findOwnReview directly
CI / test (pull_request) Successful in 14s
CI / review (gpt-4.1, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 23s
CI / review (gpt-5, security, SECURITY_REVIEW.md, SECURITY_REVIEW_TOKEN) (pull_request) Successful in 42s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m31s
The strict authorship check compared reviewer-name to User.Login which
could mismatch. The sentinel is already role-specific (e.g.
<!-- review-bot:sonnet -->) and Gitea's API blocks editing others'
comments (403). Defense-in-depth via login comparison is unnecessary
complexity that introduced a bug. Removed.
2026-05-02 11:33:57 -07:00
Rodin ecbae332f4 fix: address review findings
CI / test (pull_request) Successful in 14s
CI / review (gpt-4.1, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 23s
CI / review (gpt-5, security, SECURITY_REVIEW.md, SECURITY_REVIEW_TOKEN) (pull_request) Successful in 1m17s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m22s
- findOwnReview: skip superseded reviews, pick highest ID (most recent)
- findOwnReviewStrict: verify authorship before superseding (defense-in-depth)
- buildSupersededBody: handle empty commitSHA gracefully
- Tests: add cases for superseded skip, highest-ID selection
2026-05-02 11:30:34 -07:00
Rodin fdd75699d9 feat: always post fresh review, supersede old with collapsed body
CI / test (pull_request) Successful in 14s
CI / review (gpt-4.1, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 23s
CI / review (gpt-5, security, SECURITY_REVIEW.md, SECURITY_REVIEW_TOKEN) (pull_request) Successful in 1m2s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m25s
Closes #34

- Remove reviewUnchanged() skip logic — every push gets a fresh review
- Remove edit-in-place (PATCH same body) — always POST new
- Supersede old review: PATCH with struck-through banner + collapsed
  original body in <details> for historical reference
- Add commit footer to every review: 'Evaluated against <sha>'
- Remove --update-existing flag (no longer needed)
- Add CommitID field to Review struct
- Add TestBuildSupersededBody tests
2026-05-02 11:26:06 -07:00
Rodin 3a3c60a3c6 chore: retrigger reviews
CI / test (pull_request) Successful in 14s
CI / review (gpt-4.1, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 22s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m24s
CI / review (gpt-5, security, SECURITY_REVIEW.md, SECURITY_REVIEW_TOKEN) (pull_request) Successful in 1m20s
2026-05-02 11:19:04 -07:00
Rodin 504f616e99 fix: add coverage to .PHONY in Makefile (NIT)
CI / test (pull_request) Successful in 13s
CI / review (gpt-5, security, SECURITY_REVIEW.md, SECURITY_REVIEW_TOKEN) (pull_request) Successful in 33s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m3s
CI / review (gpt-4.1, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 22s
2026-05-02 11:17:05 -07:00
Rodin bb596db3c1 feat: improve test coverage for cmd/review-bot
CI / review (gpt-4.1, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 22s
CI / review (gpt-5, security, SECURITY_REVIEW.md, SECURITY_REVIEW_TOKEN) (pull_request) Successful in 39s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m6s
CI / test (pull_request) Successful in 14s
Partially addresses #32

- Tests for setupLogger, isPatternFile, evaluateCIStatus, envOrDefault*, validateReviewerName
- Subprocess tests for main() error paths (version, missing flags, invalid inputs)
- Integration test scaffold (build tag: integration)
- Makefile with build/test/lint/coverage targets
- Coverage: 16.7% → 42.3% for cmd/review-bot
2026-05-02 11:13:59 -07:00
Rodin d83ea4f726 feat: replace log.Printf with structured slog logging
CI / test (pull_request) Successful in 14s
CI / review (gpt-4.1, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 23s
CI / review (gpt-5, security, SECURITY_REVIEW.md, SECURITY_REVIEW_TOKEN) (pull_request) Successful in 52s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m5s
- Add --log-format flag (text/json) and --verbosity flag (debug/info/warn/error)
- Replace all log.Printf with slog.Info/Debug/Warn with structured key-value attrs
- Replace all log.Fatalf with slog.Error + os.Exit(1)
- Convert gitea/client.go warnings to slog.Warn
- Add comprehensive tests for logger initialization and level filtering

Closes #23
Partially addresses #32
2026-05-02 11:01:55 -07:00
Rodin 6c46220a53 docs: document runner requirements for composite action
CI / test (push) Successful in 14s
CI / review (gpt-4.1, gpt, GPT_REVIEW_TOKEN) (push) Has been skipped
CI / review (gpt-5, security, SECURITY_REVIEW.md, SECURITY_REVIEW_TOKEN) (push) Has been skipped
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (push) Has been skipped
Add a Runner Requirements section to the README documenting that
the composite action needs python3, sha256sum, and curl on the
runner. All are pre-installed on ubuntu-* runners but custom
images need to provide them.

Closes #12
2026-05-02 10:21:53 -07:00
Rodin 2339999d37 fix: URL-encode asset filename, truncate error body in APIError
CI / review (gpt-4.1, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 23s
CI / review (gpt-5, security, SECURITY_REVIEW.md, SECURITY_REVIEW_TOKEN) (pull_request) Successful in 51s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m21s
CI / test (pull_request) Successful in 14s
- URL-encode filename in release upload query param (MINOR)
- Truncate APIError.Body to 200 chars in Error() to avoid leaking
  verbose server responses into logs (NIT)
2026-05-02 10:02:03 -07:00
Rodin bfca28b2b2 fix: address review findings from PR #33
CI / test (pull_request) Successful in 13s
CI / review (gpt-4.1, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 22s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m9s
CI / review (gpt-5, security, SECURITY_REVIEW.md, SECURITY_REVIEW_TOKEN) (pull_request) Successful in 1m42s
- Wrap fileErr instead of err in GetAllFilesInPath fallback (MINOR)
- Use env var for asset name in release workflow to avoid quoting issues (NIT)
2026-05-02 09:58:41 -07:00
Rodin f047c994bf fix: distinguish 404 in GetAllFilesInPath, make uploads idempotent
CI / test (pull_request) Successful in 14s
CI / review (gpt-4.1, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 22s
CI / review (gpt-5, security, SECURITY_REVIEW.md, SECURITY_REVIEW_TOKEN) (pull_request) Successful in 58s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m7s
- Add APIError type with StatusCode field so callers can inspect HTTP
  status codes from Gitea API responses
- Add IsNotFound helper for ergonomic 404 checks
- GetAllFilesInPath now only falls back to single-file fetch on 404;
  all other errors (auth failures, server errors, rate limits) propagate
- Release workflow asset uploads are now idempotent: existing assets
  with the same name are deleted before re-upload on workflow re-runs

Closes #8
Closes #10
2026-05-02 09:50:35 -07:00
Rodin ceefa4c2e0 ci: use separate SECURITY_REVIEW_TOKEN for security reviewer
CI / test (pull_request) Successful in 13s
CI / review (gpt-4.1, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 23s
CI / review (gpt-5, security, SECURITY_REVIEW.md, SECURITY_REVIEW_TOKEN) (pull_request) Successful in 41s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 58s
The security-review-bot Gitea user now has its own token. This
completes the token separation so each reviewer role posts under
its own identity, enabling native Gitea multi-reviewer blocking.
2026-05-02 07:25:43 -07:00
Rodin b1f5dd4b5f fix: skip update-in-place when shared token detected
CI / test (pull_request) Successful in 14s
CI / review (gpt-4.1, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 22s
CI / review (gpt-5, security, SECURITY_REVIEW.md, SONNET_REVIEW_TOKEN) (pull_request) Successful in 39s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m21s
When hasSharedToken() detects two roles sharing the same Gitea user,
the bot now skips ALL update logic (PATCH, supersede) and always POSTs
a fresh review. This prevents clobbering a sibling's review body or
state when misconfigured.

Tests now assert return values (true/false) rather than just verifying
no panic. Added additional test case for three-roles-same-user scenario.

Addresses review feedback: update logic and review state must not
interact with sibling reviews under the same user.
2026-05-02 07:21:46 -07:00
Rodin fd179b891b fix: detect shared-token misconfiguration and warn
CI / test (pull_request) Successful in 14s
CI / review (gpt-4.1, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 24s
CI / review (gpt-5, security, SECURITY_REVIEW.md, SONNET_REVIEW_TOKEN) (pull_request) Successful in 42s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 55s
When two review-bot roles share the same Gitea user token (misconfiguration),
log a WARNING identifying which sibling is sharing. The bot continues normally
with its own honest verdict — no escalation, no deadlock. Operators see the
warning in CI logs and can fix the token setup.

Addresses Aaron's review feedback on #28: graceful degradation when someone
doesn't follow the separate-token deployment instructions.
2026-05-02 07:11:57 -07:00
Rodin b78d9972ac fix: remove worst-wins escalation logic (#28)
CI / test (pull_request) Successful in 14s
CI / review (gpt-4.1, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 23s
CI / review (gpt-5, security, SECURITY_REVIEW.md, SONNET_REVIEW_TOKEN) (pull_request) Successful in 36s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 39s
2026-05-02 07:04:33 -07:00
Rodin c2595d0263 fix: consistent url.PathEscape across all Gitea client endpoints
CI / test (pull_request) Successful in 14s
CI / review (gpt-4.1, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 21s
CI / review (gpt-5, security, SECURITY_REVIEW.md, SONNET_REVIEW_TOKEN) (pull_request) Successful in 34s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 35s
Apply url.PathEscape to owner, repo, and sha path segments in all
methods that were previously interpolating raw values. Methods already
using PathEscape (ListReviews, DeleteReview, GetTimelineReviewCommentID,
EditComment) are unchanged.

This eliminates an inconsistency flagged in PRs #17, #20, and #22 and
prevents potential path-injection bugs for names with special characters.

Closes #24
2026-05-02 02:41:51 -07:00
Rodin a9c8ecfb0b docs: add review update strategy with state transition diagram
CI / test (pull_request) Successful in 12s
CI / review (gpt-4.1, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 24s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m17s
CI / review (gpt-5, security, SECURITY_REVIEW.md, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m22s
Explains the edit-in-place approach, state transition rules, worst-wins
escalation, and inline comment lifecycle. Includes a Mermaid state
diagram for visual reference.
2026-05-01 23:01:32 -07:00
Rodin ec19622133 fix: address review findings (escalation, marshal error, redundant check)
CI / test (pull_request) Successful in 13s
CI / review (gpt-4.1, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 23s
CI / review (gpt-5, security, SECURITY_REVIEW.md, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m11s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m21s
1. First-run escalation regression (MAJOR): Add post-posting escalation
   fallback. After posting APPROVED on first run, check if a sibling
   from the same user has REQUEST_CHANGES — if so, mark ours as
   superseded and re-post as REQUEST_CHANGES.

2. json.Marshal error handling (MINOR): Return error from EditComment
   instead of ignoring it with blank identifier.

3. Redundant condition (NIT): Remove dead assignment in reviewUnchanged
   where existingEvent was assigned from r.State then compared to itself.
2026-05-01 22:50:13 -07:00
Rodin e261976dd8 feat: edit-in-place review updates (no more delete)
CI / test (pull_request) Successful in 13s
CI / review (gpt-4.1, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 23s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m37s
CI / review (gpt-5, security, SECURITY_REVIEW.md, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m43s
Replace the delete-and-repost strategy with edit-in-place:

1. No existing review → POST new (first run)
2. Same state, same body → skip entirely (threads preserved)
3. Same state, body changed → PATCH body in place via timeline API
4. State change needed → PATCH old body to "Superseded", POST new

This preserves conversation threads on inline comments. Replies to
findings are never lost. The only time a new review is posted is on
first run or when the state transitions (APPROVED ↔ REQUEST_CHANGES).

New Gitea client methods:
- EditComment: PATCH /repos/{owner}/{repo}/issues/comments/{id}
- GetTimelineReviewCommentID: finds the comment ID for a review body
  by scanning the issue timeline for the sentinel

Also simplifies shouldEscalate: removes the login parameter requirement
for pre-posting scenarios (uses findOwnReview to get login from existing
review instead).

Tests: findOwnReview (4 cases), EditComment (2 cases),
GetTimelineReviewCommentID (2 cases), shouldEscalate (8 cases updated).
2026-05-01 22:46:45 -07:00
Rodin 1c2292265b feat: skip re-posting when review is unchanged (preserve threads)
CI / test (pull_request) Successful in 14s
CI / review (gpt-4.1, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 23s
CI / review (gpt-5, security, SECURITY_REVIEW.md, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m1s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m28s
Before posting, compare the new review body+event against the existing
review with the same sentinel. If identical, skip entirely — this
preserves conversation threads on inline comments and avoids
re-notifying reviewers for findings they already know about.

Only re-posts when findings actually change (fixed, new, or different).

Tests: 6 cases covering identical, different body, different state,
stale reviews, and different sentinels.
2026-05-01 22:17:36 -07:00
Rodin b0dc6d0c09 fix: handle single-line hunks and no-newline markers in diff parser
CI / test (pull_request) Successful in 14s
CI / review (gpt-4.1, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 23s
CI / review (gpt-5, security, SECURITY_REVIEW.md, SONNET_REVIEW_TOKEN) (pull_request) Successful in 55s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m42s
- Hunk headers without comma ("@@ -1 +1 @@") now parse correctly by
  splitting on comma OR space instead of comma only
- Explicit skip for "\ No newline at end of file" lines (was already
  safe but now documents intent)
- Tests added for both edge cases (TDD: tests written first, confirmed
  failure, then fixed)

Addresses sonnet findings #1 and #2 from PR #26 review.
2026-05-01 22:10:49 -07:00
Rodin 2ac7f55396 feat: inline review comments on specific lines
CI / test (pull_request) Successful in 13s
CI / review (gpt-4.1, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 23s
CI / review (gpt-5, security, SECURITY_REVIEW.md, SONNET_REVIEW_TOKEN) (pull_request) Successful in 43s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m29s
Findings that reference a file+line within the diff are now posted as
inline comments directly on that line, in addition to appearing in the
summary table. Findings outside the diff range stay in the body only.

Implementation:
- gitea/diff.go: ParseDiffNewLines extracts new-file line numbers from
  each hunk in the unified diff
- gitea/client.go: PostReview accepts optional []ReviewComment with
  path + new_position + body (omitempty when nil)
- cmd/review-bot/main.go: maps findings → inline comments when the line
  exists in the diff, passes them to PostReview

Tests:
- diff parser: multi-hunk, new files, empty diff, boundary lines
- PostReview: with comments, nil comments (omitted from payload)
2026-05-01 21:59:21 -07:00
Rodin 55391c66d8 refactor: validate reviewer-name early (fail fast before LLM call)
CI / test (pull_request) Successful in 13s
CI / review (gpt-4.1, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 24s
CI / review (gpt-5, security, SECURITY_REVIEW.md, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m0s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m40s
Moved validateReviewerName check to right after flag parsing. Previously
it ran after the LLM request completed — wasting an expensive API call
if the name was invalid.

Sonnet review finding #1.
2026-05-01 21:42:49 -07:00
Rodin 2287a8238c feat: add role title as H1 header for visual differentiation
CI / test (pull_request) Successful in 14s
CI / review (gpt-4.1, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 19s
CI / review (gpt-5, security, SECURITY_REVIEW.md, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m27s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m42s
When reviewer-name is set, prepend "# Security Review" / "# Sonnet Review"
etc. as a top-level header. Makes it immediately obvious which role each
review represents in the Gitea UI, especially when multiple reviews come
from the same bot account.
2026-05-01 21:36:32 -07:00
Rodin 436e6a8824 fix: symlink traversal + worst-wins pre-check + user scoping
CI / test (pull_request) Successful in 14s
CI / review (gpt-4.1, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 20s
CI / review (gpt-5, security, SECURITY_REVIEW.md, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m11s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m16s
Security (MAJOR):
- Add filepath.EvalSymlinks after Clean for system-prompt-file
- Re-validate resolved path is still within workspace
- Prevents symlink → /etc/shadow exfiltration via malicious repo

Worst-wins:
- Check BEFORE posting (not after) — no delete+repost dance
- Identify sibling bots by <!-- review-bot: prefix in body
- Only escalates for bot reviews, not human REQUEST_CHANGES
- If sibling bot has REQUEST_CHANGES and we would APPROVE → post
  REQUEST_CHANGES instead

Addresses security review finding #1 (MAJOR) and sonnet finding #1.
2026-05-01 21:31:17 -07:00
Rodin 687005d982 feat: worst-wins reconciliation for shared-token review types
CI / test (pull_request) Successful in 13s
CI / review (gpt-4.1, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 24s
CI / review (gpt-5, security, SECURITY_REVIEW.md, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m9s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m18s
When multiple review types share a Gitea bot account, Gitea uses the
latest review to determine the user's approval state. This creates a
race: if security finds issues but code-quality finishes last with
APPROVE, the PR appears approved.

Now before posting, each job checks if any sibling review from the same
user has REQUEST_CHANGES. If so and we would post APPROVE, we downgrade
to COMMENT instead — the review is still visible but won't override
the blocking state.

Documented in README under "Shared Token: Worst-Wins."
2026-05-01 21:12:34 -07:00
Rodin 6a3c813279 fix: address review findings (path restriction, login cross-check, README)
CI / test (pull_request) Successful in 14s
CI / review (gpt-4.1, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 19s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m15s
CI / review (gpt-5, security, SECURITY_REVIEW.md, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m20s
- system-prompt-file: reject absolute paths and paths containing ".."
  Prevents reading arbitrary files outside the workspace on shared runners.
- Cleanup: cross-check r.User.Login == posted.User.Login before deletion
  Defense-in-depth: only attempt to delete reviews from same author.
  Flagged by both sonnet and security reviewers.
- README: fix wording (cleanup happens after posting, not before)

Issues filed for deferred work:
- #24: Consistent url.PathEscape across all client endpoints
- #25: Binary signature verification for supply-chain hardening
2026-05-01 21:05:18 -07:00
Rodin b8af8306a6 docs: comprehensive README with action usage, cleanup behavior, custom prompts
CI / test (pull_request) Successful in 14s
CI / review (gpt-4.1, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 23s
CI / review (gpt-5, security, SECURITY_REVIEW.md, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m5s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m37s
- Quick start example with composite action + matrix strategy
- Full action inputs table with descriptions
- How sentinel-based cleanup works (explains the reviewer-name concept)
- Custom prompt file usage with security review example
- CLI usage with all flags
- Environment variables table
- Token scopes documentation
- Setup guide for new repos
2026-05-01 20:59:34 -07:00
Rodin 69e0a459c3 feat: sentinel-based review cleanup + system prompt file + security review
CI / test (pull_request) Successful in 14s
CI / review (gpt-4.1, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 23s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 58s
CI / review (gpt-5, security, SECURITY_REVIEW.md, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m35s
Sentinel-based cleanup:
- Reviews embed <!-- review-bot:NAME --> in body (hidden HTML comment)
- Cleanup matches by sentinel, not token identity
- Each reviewer-name is a logical identity (sonnet, gpt, security)
- Same token can run multiple review types without conflict
- No extra API scopes needed

System prompt file (--system-prompt-file / SYSTEM_PROMPT_FILE):
- Loads a local file with additional review instructions
- Appended to system base as "Additional Review Instructions"
- Enables specialized reviews (security, performance, etc.)
- Partially addresses #5

Security review:
- SECURITY_REVIEW.md prompt focused on vulnerabilities
- 3rd CI matrix entry using same token, different prompt
- Focus: injection, auth, secrets, input validation, crypto, races

CI changes:
- REVIEWER_NAME passed from matrix.name
- SYSTEM_PROMPT_FILE passed from matrix (empty for standard reviews)
- 3 reviewers: sonnet (general), gpt (general), security (focused)
2026-05-01 20:55:09 -07:00
Rodin 41c670b44b fix: post-then-cleanup flow, remove dead code, pagination
CI / test (pull_request) Successful in 14s
CI / review (gpt-4.1, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 31s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m22s
- PostReview now returns *Review (id + user login from response)
- Delete flow: post first, then delete stale reviews by same user
- No read:user scope needed (identity from POST response)
- Removed GetAuthenticatedUser (requires scope we lack)
- ListReviews: full pagination (loops until partial page)
- envOrDefaultBool: case-insensitive, whitespace-trimmed
- action.yml: document accepted boolean values
- Tests updated for new PostReview signature
2026-05-01 20:38:21 -07:00
Rodin 0d417e068e feat: delete previous review before posting new one (#6)
CI / test (pull_request) Successful in 13s
CI / review (gpt-4.1, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 21s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m20s
Before posting a review, the bot now:
1. Calls GET /api/v1/user to identify its own login
2. Lists all reviews on the PR
3. Deletes any existing reviews from itself
4. Posts the fresh review

This keeps PR threads clean — one review per bot at any time.

New Gitea client methods:
- GetAuthenticatedUser() — token self-identification
- ListReviews() — fetch reviews on a PR
- DeleteReview() — delete a review by ID

Flag: --update-existing / UPDATE_EXISTING (default true)
Set to false to preserve old behavior (stack reviews).

All delete failures are non-fatal (logged as warnings).

Closes #6
2026-05-01 20:17:01 -07:00
Rodin 75190d53ed fix: address review findings (comment, marker budget, naming)
CI / test (pull_request) Successful in 13s
CI / review (gpt-4.1, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 22s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m48s
- UserMeta comment: "never trimmed" → "truncated only if base exceeds budget"
- Skip diff truncation marker when diffBudget < markerBudget (prevents
  marker itself from pushing EstTokens over the limit)
- Rename filepath → filePath to avoid shadowing stdlib package name
2026-05-01 20:02:35 -07:00
Rodin 8b8462bdc8 fix: address final review findings
CI / test (pull_request) Successful in 13s
CI / review (gpt-4.1, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 21s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m30s
- Comment: "~4 characters" → "~4 bytes" (len() counts bytes, not runes)
- Use utf8.RuneStart from stdlib instead of custom isUTF8Start helper
- Skip diff block entirely when Diff is empty (handles edge cases:
  draft→ready with no delta, force-push matching base, etc.)
2026-05-01 19:36:42 -07:00
Rodin 565a077b01 fix: CI config - correct patterns path, increase timeout
CI / test (pull_request) Successful in 13s
CI / review (gpt-4.1, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 21s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 2m18s
- PATTERNS_FILES: docs/ does not exist in go-patterns, use patterns/
- LLM_TIMEOUT: 600s (gpt-5-mini needs more time for larger diffs)
2026-05-01 19:06:18 -07:00
Rodin dab7871cb4 fix: address review findings on budget system
CI / test (pull_request) Successful in 14s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m41s
CI / review (gpt-5-mini, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 3m2s
- Account for truncation marker tokens when computing diff budget
  (prevents EstTokens exceeding model limit in edge cases)
- Rune-safe truncation for both UserMeta and Diff (no split multi-byte)
- Fix misleading comment (1000 chars → ~1000 tokens/4000 chars)
- Extract marker strings as constants
- Add unit tests for BuildSystemBase and BuildUserMeta
2026-05-01 18:59:07 -07:00
Rodin d9cacf6f62 fix: strict budget enforcement + deterministic model matching
CI / test (pull_request) Successful in 14s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m59s
CI / review (gpt-5-mini, gpt, GPT_REVIEW_TOKEN) (pull_request) Failing after 5m12s
Addresses review findings:
- Replace map-based model limits with ordered slice (longest-prefix-first)
  for deterministic matching
- Truncate UserMeta when base content alone exceeds budget (keeps first
  4000 chars + truncation marker)
- Remove hard minimum of 1000 tokens for diff budget — use 0 as floor
  to guarantee total never exceeds limit
- Handle zero-budget edge case (diff replaced with manual-review message)
- Add tests: huge UserMeta, all-sections-huge never exceeds limit
2026-05-01 18:51:22 -07:00
Rodin 14a0c2a946 feat: add Anthropic Messages API support (#18)
CI / test (pull_request) Successful in 13s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m2s
CI / review (gpt-5-mini, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 1m43s
Adds --llm-provider flag (openai|anthropic) to switch between API formats.

Anthropic implementation:
- POST /messages endpoint
- x-api-key + anthropic-version headers
- System prompt as top-level field (not a message)
- max_tokens: 8192 for response generation
- Parses content blocks [{type: "text", text: "..."}]

Changes:
- llm/client.go: Provider type, completeAnthropic(), doRequest() shared helper
- cmd/review-bot/main.go: --llm-provider / LLM_PROVIDER flag
- .gitea/actions/review/action.yml: llm-provider input + env
- llm/client_test.go: 4 new tests for Anthropic path

Backwards compatible — default provider is still openai.

Closes #18
2026-05-01 18:49:17 -07:00
Rodin 67d835909f feat: add context budget system for LLM overflow (#19)
CI / test (pull_request) Successful in 13s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m30s
CI / review (gpt-5-mini, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 2m29s
Adds a budget package that estimates token usage and progressively
trims context to fit within model-specific limits.

Trim order (least important first):
1. Language patterns
2. Repository conventions
3. Full file context
4. Diff (truncated as last resort)

When content is trimmed, a note is appended to the user prompt so
the LLM knows context was reduced.

- New budget package with Fit(), EstimateTokens(), LimitForModel()
- Model limit table (GPT-4.1: 128K, GPT-5: 200K, Claude: 200K)
- Refactored review/prompt.go: BuildSystemBase() and BuildUserMeta()
  extract non-trimmable content; old functions delegate to new ones
- main.go uses budget.Fit() instead of direct prompt assembly
- 7 unit tests covering all trim paths

Closes #19
2026-05-01 18:46:53 -07:00
Rodin aade891129 docs: add package-level documentation
CI / test (pull_request) Successful in 14s
CI / review (gpt-5-mini, gpt, GPT_REVIEW_TOKEN) (pull_request) Failing after 1m6s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Failing after 55s
Per go-patterns/package-design.md, every package needs a doc comment.
Added to gitea, llm, and review packages.
2026-05-01 14:54:58 -07:00
Rodin 7b42de67ca fix: handle empty path in ListContents (root listing)
CI / test (pull_request) Successful in 14s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m4s
CI / review (gpt-5-mini, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 1m8s
Empty path now yields /contents instead of /contents/ (trailing slash).
Added doc comment noting empty path = repo root.
2026-05-01 14:46:40 -07:00
Rodin dd2661fe14 fix: address all review findings from PR #17
CI / test (pull_request) Successful in 13s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 52s
CI / review (gpt-5-mini, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 1m12s
- Rename all remaining url locals to reqURL (consistency)
- Use http.MethodGet/http.MethodPost constants
- Document escapePath: relative paths only, double-encoding expected
- Add TestEscapePath with 7 edge cases (empty, spaces, #, deep, encoded)
2026-05-01 14:40:19 -07:00
Rodin 98a4772f30 fix: path-escape file paths and eliminate url package shadowing
CI / test (pull_request) Successful in 14s
CI / review (gpt-5-mini, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 1m48s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 55s
- Add escapePath() helper: escapes each path segment individually
  (preserves slashes as separators, escapes spaces/#/? etc)
- Apply to GetFileContent, GetFileContentRef, ListContents
- Rename doGet parameter from url to reqURL (avoids shadowing net/url)
- Rename local variables in GetFileContent/ListContents for consistency

Addresses remaining findings from PR #16 review.
2026-05-01 14:33:18 -07:00
Rodin b02ade4f23 fix: quick wins (#7, #9, #13)
CI / test (pull_request) Successful in 13s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 59s
CI / review (gpt-5-mini, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 1m48s
- Add --version flag and log version on startup (closes #9)
- URL-escape ref query parameter in GetFileContentRef (closes #7)
- Add go vet to release workflow (closes #13)

Renamed local url variable to reqURL to avoid shadowing net/url package.
2026-05-01 14:19:37 -07:00
Rodin 69e70466fd fix: address all review findings (context timeout, docs, early exit)
CI / test (pull_request) Successful in 14s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m7s
CI / review (gpt-5-mini, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 1m40s
- Overall context timeout now derived from LLM timeout + 1 minute
  (no longer hardcoded 3min that could conflict with longer LLM timeouts)
- Clarify concurrency docs: With* methods are setup-only, not concurrent
- Add ctx.Err() checks in fetchFileContext and fetchPatterns loops
  (break early on cancellation instead of making unnecessary requests)
2026-05-01 13:26:19 -07:00
Rodin 0cca44b65a fix: address all remaining review findings on PR #14
CI / test (pull_request) Successful in 13s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m29s
CI / review (gpt-5-mini, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 1m36s
- Fix doc comments: WithTimeout and WithTemperature each get their own
- Add TestWithTimeout (verifies short timeout causes request failure)
- Log warning on directory recursion failure in GetAllFilesInPath
- Note: unexported fields is a breaking change, will document in release notes
2026-05-01 13:17:39 -07:00
Rodin 43041a00f5 fix: rewrite action.yml (was corrupted with duplicate keys)
CI / test (pull_request) Successful in 11s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m34s
CI / review (gpt-5-mini, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 1m33s
Clean single definition of all inputs: temperature, timeout,
patterns-repo, patterns-files. Also added runner requirements
comment at the top.
2026-05-01 13:08:18 -07:00
Rodin 1da61e514d feat: make LLM timeout configurable (default 5min)
CI / test (pull_request) Successful in 13s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m6s
CI / review (gpt-5-mini, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 1m14s
New flag: --llm-timeout / LLM_TIMEOUT (seconds, default 300)
New builder: llmClient.WithTimeout(duration)
Composite action: new timeout input

Keeps 5 minutes as the sensible default but allows tuning for
larger repos or slower models.
2026-05-01 13:04:00 -07:00
Rodin 401e94d3e4 fix: increase LLM client timeout to 5 minutes
CI / test (pull_request) Successful in 13s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m13s
CI / review (gpt-5-mini, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 1m43s
GPT-5-mini timed out on larger diffs (2min was too short).
LLM calls for code review with full file context can take 2-4min.
2026-05-01 13:00:36 -07:00
Rodin cedb5e7b90 fix: address all review findings on PR #14
CI / test (pull_request) Successful in 13s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 48s
CI / review (gpt-5-mini, gpt, GPT_REVIEW_TOKEN) (pull_request) Failing after 2m12s
- gitea.Client: add concurrency safety doc comment
- gitea.Client: set 30s HTTP client timeout as safety net
- llm.Client: add concurrency safety doc comment
- llm.Client: set 2min HTTP client timeout (LLM calls are slow)
- gitea/client.go: gofmt to fix indentation
- integration_test: update to current BuildSystemPrompt/BuildUserPrompt signatures
- integration_test: use strings.SplitN for owner/repo parsing
2026-05-01 12:56:07 -07:00
Rodin ecebd52371 fix: log warnings instead of swallowing errors
CI / test (pull_request) Successful in 13s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m29s
CI / review (gpt-5-mini, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 1m44s
- GetAllFilesInPath: log.Printf when file fetch or dir recursion fails
- integration_test: use strings.SplitN for owner/repo parsing (idiomatic)

Addresses GPT review findings #1, #2.
2026-05-01 12:45:52 -07:00
Rodin 27e0056f29 feat: add context.Context + unexport client fields
CI / test (pull_request) Successful in 13s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 54s
CI / review (gpt-5-mini, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 1m22s
REVIEW.md findings 1-4, 14:
- All Gitea client methods now accept context.Context as first param
- All LLM client methods now accept context.Context as first param
- Use http.NewRequestWithContext for cancellation/timeout support
- Main uses 3-minute timeout context for all operations
- Unexport Client struct fields (baseURL, token, apiKey, etc.)
- Use bytes.NewReader instead of strings.NewReader(string(...))
2026-05-01 12:31:41 -07:00
Rodinandaweiker 9aec7ff952 docs: add comprehensive code review report (vs go-patterns)
CI / test (pull_request) Successful in 14s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Failing after 1m4s
CI / review (gpt-5-mini, gpt, GPT_REVIEW_TOKEN) (pull_request) Failing after 3m47s
2026-05-01 19:24:41 +00:00
Rodin f77ea171c3 ci: add go-patterns as review reference
CI / test (pull_request) Successful in 13s
CI / review (gpt-5-mini, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 1m43s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m50s
Self-reviews now use rodin/go-patterns (README.md + docs/) as
language idiom criteria alongside CONVENTIONS.md.
2026-05-01 12:15:25 -07:00
Rodin 56f5abda3c feat: multi-repo patterns + directory recursion
CI / test (pull_request) Successful in 14s
CI / review (gpt-5-mini, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 1m57s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 2m2s
patterns-repo now accepts a comma-separated list of repos:
  PATTERNS_REPO="rodin/elixir-patterns,rodin/phoenix-conventions"

patterns-files accepts files AND directories:
  PATTERNS_FILES="README.md,docs/"

When a path is a directory, all files within it are fetched
recursively via the Gitea contents API. Only .md, .txt, .yml,
and .yaml files are included as pattern content.

New API methods:
- ListContents: list files/dirs at a path via contents API
- GetAllFilesInPath: recursively fetch all file contents

This allows a single review action to pull idioms from multiple
pattern repos (e.g. elixir-patterns + phoenix-conventions) and
include entire directories of documentation as review criteria.
2026-05-01 12:14:19 -07:00
Rodin e234dca474 feat: full file context + patterns-repo support
CI / test (pull_request) Successful in 13s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m51s
CI / review (gpt-5-mini, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 2m0s
Major improvements to review quality:

1. Full file context: fetch complete content of all modified files from
   the PR branch and include as reference. This eliminates false-positive
   "missing import" findings since the model sees the entire file.

2. Patterns repo: new --patterns-repo / PATTERNS_REPO flag fetches
   language idiom files from a separate Gitea repo (e.g. rodin/elixir-patterns)
   and includes them as review criteria.

3. Multi-file patterns: --patterns-files / PATTERNS_FILES accepts
   comma-separated file paths to fetch from the patterns repo.

New API methods:
- GetPullRequestFiles: list changed files in a PR
- GetFileContentRef: fetch file content from a specific branch/ref

Prompt changes:
- BuildSystemPrompt now accepts (conventions, patterns)
- BuildUserPrompt now accepts fileContext parameter
- File context displayed before diff for model reference
- Patterns presented as "review criteria" in system prompt

Composite action updated with patterns-repo and patterns-files inputs.
2026-05-01 12:11:49 -07:00
Rodin c76362af95 fix: prevent false-positive missing-import findings
CI / test (pull_request) Successful in 13s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m59s
CI / review (gpt-5-mini, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 3m36s
The LLM was treating the diff as complete file context and flagging
"missing imports" for symbols that exist in unchanged portions of
the file. Added explicit instructions to the system prompt:

- Clarify that the diff is partial; unchanged code already exists
- Explicitly instruct: do not flag missing imports/types unless the
  diff introduces a new usage without a corresponding addition
- Add rule: never flag undefined symbols that could exist in the
  unchanged portions
2026-05-01 12:00:27 -07:00
Rodin 46c63ed121 fix: address all review findings (zero remaining)
CI / test (pull_request) Successful in 13s
CI / review (gpt-5-mini, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 1m43s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 2m19s
Tests:
- Add WithTemperature tests (builder method, chaining, zero omission)
- Add temperature serialization tests (omitted when 0, included when set)

Composite action:
- Use python3 for robust JSON version parsing (replaces sed)
- Verify SHA-256 checksum before executing downloaded binary
- Wire up repo input (no longer hardcodes rodin/review-bot)

Release workflow:
- Handle 409 conflict (existing release for tag)
- Use file-based JSON parsing for reliability

Code:
- Tighten WithTemperature doc comment (single clear line)
- Fix flag alignment (missing tab on llmTemp declaration)
2026-05-01 11:58:21 -07:00
Rodin 59fbd38837 fix: address all remaining review findings
CI / test (pull_request) Successful in 14s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 2m20s
CI / review (gpt-5-mini, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 2m20s
- Add temperature range validation (must be 0-2, fatal on invalid)
- release.yml: use python3 for robust JSON parsing instead of sed
- Composite action: add header comment confirming Gitea Actions compat
- All findings from review #385 addressed
2026-05-01 11:40:15 -07:00
Rodin 8d53b649ee fix: address review findings (cache path, docs)
CI / test (pull_request) Successful in 13s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 2m13s
CI / review (gpt-5-mini, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 2m48s
- Composite action: cache to runner.temp instead of /usr/local/bin
  (avoids permission issues on runners)
- Document that temperature=0 means server default (omitted from request)
- Note: strconv import already exists (false positive from GPT-5)
2026-05-01 11:38:28 -07:00
Rodin c458587cfc feat: add composite action for clean distribution
CI / test (pull_request) Successful in 13s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 2m28s
CI / review (gpt-5-mini, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 2m43s
- .gitea/actions/review/action.yml: composite action with caching
  Consumers just use:
    uses: https://gitea.weiker.me/rodin/review-bot/.gitea/actions/[email protected]
  No Go toolchain needed, binary cached by version tag.

- Remove install.sh (replaced by composite action)
- CI workflow: use matrix strategy to parallelize reviews
- Self-review still builds from source (pre-release)
2026-05-01 11:32:15 -07:00
Rodin 4b3cac66c3 fix: address review findings
CI / test (pull_request) Successful in 14s
CI / review (pull_request) Successful in 5m3s
- install.sh: verify SHA-256 checksum before installing binary
- install.sh: fallback to ~/.local/bin if /usr/local/bin not writable
- install.sh: use sed instead of grep for POSIX-safe JSON parsing
- release.yml: remove jq dependency, parse release ID with sed
- llm: make temperature configurable via --llm-temperature / LLM_TEMPERATURE
- llm: add WithTemperature builder method on Client
- llm: omit temperature from request when zero (uses server default)
2026-05-01 11:22:31 -07:00
Rodin b6277216f7 fix: remove hardcoded temperature (unsupported by GPT-5)
CI / test (pull_request) Successful in 14s
CI / review (pull_request) Successful in 4m56s
GPT-5 via SAP AI Core only supports temperature=1 (default).
Remove the hardcoded 0.1 and use omitempty so the field is not sent.
2026-05-01 11:15:08 -07:00
Rodin 99916fe24a fix(ci): use GPT models available via HAI proxy
CI / test (pull_request) Successful in 13s
CI / review (pull_request) Failing after 12s
HAI proxy serves Anthropic on a different path (/anthropic/v1) than
OpenAI (/openai/v1). Until review-bot supports multiple base URLs,
use GPT-5 and GPT-5-mini for both review slots.
2026-05-01 11:02:27 -07:00
Rodin d62e8ee4f0 ci: retrigger after adding secrets
CI / test (pull_request) Successful in 14s
CI / review (pull_request) Failing after 12s
2026-05-01 10:40:39 -07:00
Rodin 0568a84aa9 ci: add release workflow + install script
CI / test (pull_request) Successful in 14s
CI / review (pull_request) Failing after 11s
- Release workflow: builds linux/darwin amd64/arm64 on tag push
- Injects version via -ldflags
- Creates Gitea release with binary assets + checksums
- install.sh: curl-pipe-bash installer from latest release
- Version variable in main.go for -version flag support
2026-05-01 10:36:23 -07:00
Rodin 3c536c42d5 Add unit tests, integration test, CI workflow, and conventions
CI / test (push) Successful in 18s
CI / review (push) Has been skipped
- gitea/client_test.go: mock HTTP tests for all API methods + error cases
- llm/client_test.go: mock tests for completion, errors, timeouts
- review/parser_test.go: JSON parsing, markdown fences, validation
- review/formatter_test.go: markdown output, empty/multiple findings
- review/prompt_test.go: system/user prompt construction
- integration_test.go: full end-to-end flow (build tag: integration)
- .gitea/workflows/ci.yml: test + vet + build on push, dual LLM review on PRs
- CONVENTIONS.md: coding standards for self-review dogfooding
- README.md: usage docs, env vars, architecture
2026-05-01 10:03:44 -07:00
Rodin 700f186023 Initial implementation: AI code review bot for Gitea
- CLI binary with flag/env var configuration
- Gitea API client (PR metadata, diff, CI status, post review)
- OpenAI-compatible LLM client
- Structured review prompt with conventions support
- JSON response parser with validation
- Markdown review formatter for Gitea
- CI failure auto-detection (REQUEST_CHANGES)
- Dry-run mode for testing
2026-05-01 09:42:45 -07:00