neon_sakura 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.ai-reviewer/README.md +182 -0
- data/.ai-reviewer/ai-reviewer.sh +56 -0
- data/.ai-reviewer/build-system-prompt.sh +136 -0
- data/.ai-reviewer/extract-claude-sections.sh +32 -0
- data/.ai-reviewer/test-ai-reviewer.sh +40 -0
- data/.ai-reviewer-config.yml +190 -0
- data/.github/dependabot.yml +12 -0
- data/.github/settings.yml +70 -0
- data/.github/workflows/ai-pr-review-on-comment.yml +384 -0
- data/.github/workflows/ai-pr-review.yml +328 -0
- data/.github/workflows/license-check.yml +78 -0
- data/.github/workflows/lint.yml +79 -0
- data/.github/workflows/security.yml +131 -0
- data/.github/workflows/semgrep.yml +26 -0
- data/.github/workflows/test.yml +44 -0
- data/.gitignore +75 -0
- data/.rubocop.yml +33 -0
- data/.ruby-version +1 -0
- data/.simplecov +14 -0
- data/.stylelintignore +10 -0
- data/.stylelintrc.json +37 -0
- data/AGENTS.md +51 -0
- data/CHANGELOG.md +568 -0
- data/CLAUDE.md +632 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +327 -0
- data/LICENSE +21 -0
- data/README.md +1209 -0
- data/Rakefile +25 -0
- data/app/assets/images/cherry_blossom.svg +1525 -0
- data/app/assets/images/cherry_blossom_tree.png +0 -0
- data/app/assets/images/prysm-icon.png +0 -0
- data/app/assets/stylesheets/base.css +29 -0
- data/app/assets/stylesheets/components.css +1652 -0
- data/app/assets/stylesheets/forms.css +152 -0
- data/app/assets/stylesheets/loading.css +145 -0
- data/app/assets/stylesheets/neon_sakura.css +40 -0
- data/app/assets/stylesheets/pagy-tailwind.css +120 -0
- data/app/assets/stylesheets/theme-default.css +40 -0
- data/app/assets/stylesheets/theme-green.css +84 -0
- data/app/assets/stylesheets/theme-purple.css +94 -0
- data/app/assets/stylesheets/theme-red.css +84 -0
- data/app/assets/stylesheets/utility-borders.css +29 -0
- data/app/assets/stylesheets/utility-colors.css +185 -0
- data/app/assets/stylesheets/utility-effects.css +123 -0
- data/app/assets/stylesheets/utility-gradients.css +158 -0
- data/app/assets/stylesheets/utility-layout.css +132 -0
- data/app/assets/stylesheets/utility-reset.css +13 -0
- data/app/assets/stylesheets/utility-responsive.css +145 -0
- data/app/assets/stylesheets/utility-sizing.css +99 -0
- data/app/assets/stylesheets/utility-spacing.css +174 -0
- data/app/assets/stylesheets/utility-typography.css +97 -0
- data/app/controllers/errors_controller.rb +120 -0
- data/app/controllers/style_guide_controller.rb +117 -0
- data/app/helpers/errors_helper.rb +12 -0
- data/app/helpers/neon_sakura/navbar_helper.rb +43 -0
- data/app/helpers/style_guide_helper.rb +36 -0
- data/app/javascript/neon_sakura/dropdown.js +22 -0
- data/app/javascript/neon_sakura/navbar.js +71 -0
- data/app/javascript/neon_sakura/theme_switcher.js +187 -0
- data/app/views/errors/show.html.erb +105 -0
- data/app/views/layouts/error.html.erb +19 -0
- data/app/views/layouts/mission_control/jobs/_application_selection.html.erb +14 -0
- data/app/views/layouts/mission_control/jobs/_navigation.html.erb +21 -0
- data/app/views/layouts/mission_control/jobs/application.html.erb +453 -0
- data/app/views/layouts/style_guide.html.erb +416 -0
- data/app/views/shared/_file_upload.html.erb +184 -0
- data/app/views/shared/_footer.html.erb +23 -0
- data/app/views/shared/_header.html.erb +42 -0
- data/app/views/shared/_navbar.html.erb +306 -0
- data/app/views/shared/_profile_image_selector.html.erb +165 -0
- data/app/views/shared/_theme_switcher.html.erb +64 -0
- data/app/views/shared/icons/_adjustments.html.erb +10 -0
- data/app/views/shared/icons/_alert_circle.html.erb +3 -0
- data/app/views/shared/icons/_alert_triangle.html.erb +3 -0
- data/app/views/shared/icons/_archive.html.erb +3 -0
- data/app/views/shared/icons/_arrow_down.html.erb +3 -0
- data/app/views/shared/icons/_arrow_left.html.erb +3 -0
- data/app/views/shared/icons/_arrow_up.html.erb +3 -0
- data/app/views/shared/icons/_arrows_pointing_in.html.erb +10 -0
- data/app/views/shared/icons/_arrows_pointing_out.html.erb +10 -0
- data/app/views/shared/icons/_artemis_logo.html.erb +26 -0
- data/app/views/shared/icons/_auth_banner.html.erb +1 -0
- data/app/views/shared/icons/_bars.html.erb +10 -0
- data/app/views/shared/icons/_bell.html.erb +3 -0
- data/app/views/shared/icons/_book.html.erb +3 -0
- data/app/views/shared/icons/_bookmark.html.erb +3 -0
- data/app/views/shared/icons/_box.html.erb +3 -0
- data/app/views/shared/icons/_brain.html.erb +3 -0
- data/app/views/shared/icons/_briefcase.html.erb +3 -0
- data/app/views/shared/icons/_calendar.html.erb +3 -0
- data/app/views/shared/icons/_camera.html.erb +4 -0
- data/app/views/shared/icons/_chart_bar.html.erb +3 -0
- data/app/views/shared/icons/_chart_line.html.erb +10 -0
- data/app/views/shared/icons/_chart_pie.html.erb +11 -0
- data/app/views/shared/icons/_chat.html.erb +3 -0
- data/app/views/shared/icons/_check.html.erb +3 -0
- data/app/views/shared/icons/_check_circle.html.erb +3 -0
- data/app/views/shared/icons/_cherry_blossom.html.erb +1516 -0
- data/app/views/shared/icons/_cherry_blossom_silhouette.html.erb +1016 -0
- data/app/views/shared/icons/_cherry_blossom_single_flower.html.erb +1125 -0
- data/app/views/shared/icons/_cherry_blossom_tree.html.erb +159 -0
- data/app/views/shared/icons/_chevron_down.html.erb +3 -0
- data/app/views/shared/icons/_chevron_right.html.erb +9 -0
- data/app/views/shared/icons/_clipboard.html.erb +3 -0
- data/app/views/shared/icons/_clock.html.erb +3 -0
- data/app/views/shared/icons/_close.html.erb +3 -0
- data/app/views/shared/icons/_cog.html.erb +4 -0
- data/app/views/shared/icons/_crop.html.erb +10 -0
- data/app/views/shared/icons/_crown.html.erb +3 -0
- data/app/views/shared/icons/_disc.html.erb +3 -0
- data/app/views/shared/icons/_download.html.erb +3 -0
- data/app/views/shared/icons/_dragonfly.html.erb +58 -0
- data/app/views/shared/icons/_duplicate.html.erb +4 -0
- data/app/views/shared/icons/_edit.html.erb +3 -0
- data/app/views/shared/icons/_envelope.html.erb +3 -0
- data/app/views/shared/icons/_eraser.html.erb +10 -0
- data/app/views/shared/icons/_external_link.html.erb +3 -0
- data/app/views/shared/icons/_eye.html.erb +4 -0
- data/app/views/shared/icons/_file_csv.html.erb +10 -0
- data/app/views/shared/icons/_file_export.html.erb +10 -0
- data/app/views/shared/icons/_file_image.html.erb +10 -0
- data/app/views/shared/icons/_file_import.html.erb +10 -0
- data/app/views/shared/icons/_file_question.html.erb +6 -0
- data/app/views/shared/icons/_film.html.erb +3 -0
- data/app/views/shared/icons/_filter.html.erb +3 -0
- data/app/views/shared/icons/_folder.html.erb +3 -0
- data/app/views/shared/icons/_folder_open.html.erb +3 -0
- data/app/views/shared/icons/_folder_plus.html.erb +3 -0
- data/app/views/shared/icons/_globe.html.erb +3 -0
- data/app/views/shared/icons/_google.html.erb +11 -0
- data/app/views/shared/icons/_heart.html.erb +3 -0
- data/app/views/shared/icons/_heart_broken.html.erb +11 -0
- data/app/views/shared/icons/_heart_pulse.html.erb +4 -0
- data/app/views/shared/icons/_history.html.erb +11 -0
- data/app/views/shared/icons/_home.html.erb +10 -0
- data/app/views/shared/icons/_image.html.erb +3 -0
- data/app/views/shared/icons/_inbox.html.erb +3 -0
- data/app/views/shared/icons/_info_circle.html.erb +10 -0
- data/app/views/shared/icons/_key.html.erb +3 -0
- data/app/views/shared/icons/_layers.html.erb +10 -0
- data/app/views/shared/icons/_lightbulb.html.erb +10 -0
- data/app/views/shared/icons/_lightning.html.erb +3 -0
- data/app/views/shared/icons/_list.html.erb +3 -0
- data/app/views/shared/icons/_lock.html.erb +3 -0
- data/app/views/shared/icons/_logout.html.erb +3 -0
- data/app/views/shared/icons/_magazine.html.erb +3 -0
- data/app/views/shared/icons/_magic.html.erb +3 -0
- data/app/views/shared/icons/_minus.html.erb +10 -0
- data/app/views/shared/icons/_mobile.html.erb +10 -0
- data/app/views/shared/icons/_moon.html.erb +3 -0
- data/app/views/shared/icons/_network.html.erb +10 -0
- data/app/views/shared/icons/_new_item_banner.html.erb +1 -0
- data/app/views/shared/icons/_ouroboros.html.erb +24 -0
- data/app/views/shared/icons/_package.html.erb +3 -0
- data/app/views/shared/icons/_palette.html.erb +3 -0
- data/app/views/shared/icons/_paper_plane.html.erb +10 -0
- data/app/views/shared/icons/_photo.html.erb +10 -0
- data/app/views/shared/icons/_play.html.erb +4 -0
- data/app/views/shared/icons/_plus.html.erb +3 -0
- data/app/views/shared/icons/_pocket.html.erb +11 -0
- data/app/views/shared/icons/_prysm-icon.html.erb +34 -0
- data/app/views/shared/icons/_prysm.html.erb +13 -0
- data/app/views/shared/icons/_pushbullet-1.html.erb +29 -0
- data/app/views/shared/icons/_pushbullet-2.html.erb +2 -0
- data/app/views/shared/icons/_puzzle.html.erb +10 -0
- data/app/views/shared/icons/_qrcode.html.erb +3 -0
- data/app/views/shared/icons/_question.html.erb +3 -0
- data/app/views/shared/icons/_receipt.html.erb +10 -0
- data/app/views/shared/icons/_redo.html.erb +3 -0
- data/app/views/shared/icons/_refresh.html.erb +3 -0
- data/app/views/shared/icons/_rocket.html.erb +10 -0
- data/app/views/shared/icons/_rss.html.erb +3 -0
- data/app/views/shared/icons/_save.html.erb +3 -0
- data/app/views/shared/icons/_search.html.erb +3 -0
- data/app/views/shared/icons/_search_minus.html.erb +10 -0
- data/app/views/shared/icons/_search_plus.html.erb +10 -0
- data/app/views/shared/icons/_server_error.html.erb +6 -0
- data/app/views/shared/icons/_share.html.erb +3 -0
- data/app/views/shared/icons/_shield_check.html.erb +3 -0
- data/app/views/shared/icons/_sign_in.html.erb +3 -0
- data/app/views/shared/icons/_spinner.html.erb +4 -0
- data/app/views/shared/icons/_star.html.erb +3 -0
- data/app/views/shared/icons/_store.html.erb +10 -0
- data/app/views/shared/icons/_sun.html.erb +3 -0
- data/app/views/shared/icons/_sync.html.erb +3 -0
- data/app/views/shared/icons/_table.html.erb +3 -0
- data/app/views/shared/icons/_tag.html.erb +3 -0
- data/app/views/shared/icons/_tags.html.erb +11 -0
- data/app/views/shared/icons/_tools.html.erb +4 -0
- data/app/views/shared/icons/_trash.html.erb +3 -0
- data/app/views/shared/icons/_undo.html.erb +3 -0
- data/app/views/shared/icons/_unlock.html.erb +3 -0
- data/app/views/shared/icons/_upload.html.erb +3 -0
- data/app/views/shared/icons/_user.html.erb +3 -0
- data/app/views/shared/icons/_user_circle.html.erb +10 -0
- data/app/views/shared/icons/_user_plus.html.erb +10 -0
- data/app/views/shared/icons/_video.html.erb +3 -0
- data/app/views/shared/icons/_wrench.html.erb +11 -0
- data/app/views/style_guide/index.html.erb +77 -0
- data/app/views/style_guide/sections/_alerts.html.erb +114 -0
- data/app/views/style_guide/sections/_badges.html.erb +78 -0
- data/app/views/style_guide/sections/_buttons.html.erb +130 -0
- data/app/views/style_guide/sections/_cards.html.erb +84 -0
- data/app/views/style_guide/sections/_colors.html.erb +106 -0
- data/app/views/style_guide/sections/_file_upload.html.erb +135 -0
- data/app/views/style_guide/sections/_forms.html.erb +129 -0
- data/app/views/style_guide/sections/_gradients.html.erb +253 -0
- data/app/views/style_guide/sections/_header.html.erb +12 -0
- data/app/views/style_guide/sections/_icons.html.erb +55 -0
- data/app/views/style_guide/sections/_images.html.erb +40 -0
- data/app/views/style_guide/sections/_loading.html.erb +242 -0
- data/app/views/style_guide/sections/_pagination.html.erb +212 -0
- data/app/views/style_guide/sections/_profile_components.html.erb +203 -0
- data/app/views/style_guide/sections/_theme_switcher.html.erb +72 -0
- data/app/views/style_guide/sections/_typography.html.erb +65 -0
- data/bin/ai-optimize-claude-md +540 -0
- data/bin/ai-review-local +345 -0
- data/bin/ai-security-review +585 -0
- data/bin/brakeman +9 -0
- data/bin/install-hooks +57 -0
- data/bin/rake +7 -0
- data/bin/rubocop +10 -0
- data/bin/verify_setup.rb +31 -0
- data/config/brakeman.ignore +28 -0
- data/config/initializers/neon_sakura.rb +15 -0
- data/config/license_overrides.yml +13 -0
- data/config/routes.rb +21 -0
- data/config/theme_mappings.yml +61 -0
- data/docs/PRYSM_ASSETS.md +210 -0
- data/docs/plans/extract_ai_reviewer_plan.md +151 -0
- data/docs/plans/neon_sakura_gem_plan.md +138 -0
- data/lib/neon_sakura/configuration.rb +94 -0
- data/lib/neon_sakura/engine.rb +48 -0
- data/lib/neon_sakura/icon_helper.rb +54 -0
- data/lib/neon_sakura/profile_helper.rb +24 -0
- data/lib/neon_sakura/stylesheet_helper.rb +40 -0
- data/lib/neon_sakura/theme_helper.rb +63 -0
- data/lib/neon_sakura/theme_importer.rb +112 -0
- data/lib/neon_sakura/version.rb +5 -0
- data/lib/neon_sakura.rb +13 -0
- data/neon_sakura.gemspec +50 -0
- data/package.json +18 -0
- data/scripts/git-hooks/post-merge +132 -0
- data/scripts/git-hooks/pre-commit +123 -0
- data/scripts/git-hooks/pre-push +127 -0
- data/scripts/license-check.rb +587 -0
- data/settings.local.json +12 -0
- data/yarn.lock +778 -0
- metadata +503 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# GitHub Repository Settings
|
|
2
|
+
# This file can be used with the Probot Settings app: https://github.com/apps/settings
|
|
3
|
+
# Or manually configure these settings in your repository
|
|
4
|
+
|
|
5
|
+
repository:
|
|
6
|
+
# Repository name
|
|
7
|
+
name: neon_sakura
|
|
8
|
+
|
|
9
|
+
# Repository description
|
|
10
|
+
description: A Rails 8 application with advanced search and download capabilities
|
|
11
|
+
|
|
12
|
+
# Repository homepage
|
|
13
|
+
homepage: https://github.com/TRex22/neon_sakura
|
|
14
|
+
|
|
15
|
+
# Repository topics
|
|
16
|
+
topics: ruby, rails, rails8, yjit, sqlite, solidqueue
|
|
17
|
+
|
|
18
|
+
# Either true to enable automated security fixes, or false to disable
|
|
19
|
+
enable_automated_security_fixes: true
|
|
20
|
+
|
|
21
|
+
# Either true to enable vulnerability alerts, or false to disable
|
|
22
|
+
enable_vulnerability_alerts: true
|
|
23
|
+
|
|
24
|
+
# Collaborators: none configured (manage via GitHub UI)
|
|
25
|
+
|
|
26
|
+
# See https://docs.github.com/en/rest/reference/repos#update-a-repository for all available settings
|
|
27
|
+
|
|
28
|
+
# Branch protection rules
|
|
29
|
+
branches:
|
|
30
|
+
- name: main
|
|
31
|
+
# Required. Require at least one approving review on a pull request, before merging.
|
|
32
|
+
protection:
|
|
33
|
+
# Required. Require status checks to pass before merging.
|
|
34
|
+
required_status_checks:
|
|
35
|
+
# Required. Require branches to be up to date before merging.
|
|
36
|
+
strict: true
|
|
37
|
+
# Required. The list of status checks to require in order to merge into this branch
|
|
38
|
+
contexts:
|
|
39
|
+
- scan_ruby
|
|
40
|
+
- scan_js
|
|
41
|
+
- lint
|
|
42
|
+
- test
|
|
43
|
+
- system-test
|
|
44
|
+
|
|
45
|
+
# Required. Enforce all configured restrictions for administrators.
|
|
46
|
+
enforce_admins: false
|
|
47
|
+
|
|
48
|
+
# Required. Require at least one approving review on a pull request, before merging.
|
|
49
|
+
required_pull_request_reviews:
|
|
50
|
+
# Specify if approved reviews should be dismissed when new commits are pushed
|
|
51
|
+
dismiss_stale_reviews: true
|
|
52
|
+
# Specify if new reviewable commits should dismiss approved reviews
|
|
53
|
+
require_code_owner_reviews: false
|
|
54
|
+
# Specify the number of reviewers required to approve pull requests
|
|
55
|
+
required_approving_review_count: 0
|
|
56
|
+
|
|
57
|
+
# Required. Restrict who can push to this branch. Team and user restrictions are only available for organization-owned repositories. Set to null to disable.
|
|
58
|
+
restrictions: null
|
|
59
|
+
|
|
60
|
+
# Required. Require linear history
|
|
61
|
+
required_linear_history: false
|
|
62
|
+
|
|
63
|
+
# Permits force pushes to the protected branch by anyone with write access to the repository.
|
|
64
|
+
allow_force_pushes: false
|
|
65
|
+
|
|
66
|
+
# Allows deletion of the protected branch by anyone with write access to the repository.
|
|
67
|
+
allow_deletions: false
|
|
68
|
+
|
|
69
|
+
# Required. Allow specified users and teams to bypass required pull requests
|
|
70
|
+
required_conversation_resolution: true
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
name: AI PR Review on Comment
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
issue_comment:
|
|
5
|
+
types: [created]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
pull-requests: write
|
|
10
|
+
issues: write
|
|
11
|
+
models: read
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
ai-review-on-comment:
|
|
15
|
+
# Only run on PR comments that mention the bot
|
|
16
|
+
if: |
|
|
17
|
+
github.event.issue.pull_request &&
|
|
18
|
+
(contains(github.event.comment.body, '@ai-review') ||
|
|
19
|
+
contains(github.event.comment.body, '/review'))
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
|
|
22
|
+
steps:
|
|
23
|
+
- name: React to comment
|
|
24
|
+
uses: actions/github-script@v8
|
|
25
|
+
with:
|
|
26
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
27
|
+
script: |
|
|
28
|
+
await github.rest.reactions.createForIssueComment({
|
|
29
|
+
owner: context.repo.owner,
|
|
30
|
+
repo: context.repo.repo,
|
|
31
|
+
comment_id: context.payload.comment.id,
|
|
32
|
+
content: 'rocket'
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
- name: Get PR details
|
|
36
|
+
id: pr-details
|
|
37
|
+
uses: actions/github-script@v8
|
|
38
|
+
with:
|
|
39
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
40
|
+
script: |
|
|
41
|
+
const pr = await github.rest.pulls.get({
|
|
42
|
+
owner: context.repo.owner,
|
|
43
|
+
repo: context.repo.repo,
|
|
44
|
+
pull_number: context.issue.number
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
core.setOutput('base_ref', pr.data.base.ref);
|
|
48
|
+
core.setOutput('head_ref', pr.data.head.ref);
|
|
49
|
+
core.setOutput('head_sha', pr.data.head.sha);
|
|
50
|
+
core.setOutput('pr_title', pr.data.title);
|
|
51
|
+
core.setOutput('pr_body', pr.data.body || '');
|
|
52
|
+
return pr.data;
|
|
53
|
+
|
|
54
|
+
- name: Checkout PR code
|
|
55
|
+
uses: actions/checkout@v6
|
|
56
|
+
with:
|
|
57
|
+
ref: ${{ steps.pr-details.outputs.head_sha }}
|
|
58
|
+
fetch-depth: 0
|
|
59
|
+
|
|
60
|
+
- name: Get previous AI reviews and comments
|
|
61
|
+
id: previous-reviews
|
|
62
|
+
uses: actions/github-script@v8
|
|
63
|
+
with:
|
|
64
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
65
|
+
script: |
|
|
66
|
+
// Fetch both PR comments and review comments
|
|
67
|
+
const comments = await github.rest.issues.listComments({
|
|
68
|
+
owner: context.repo.owner,
|
|
69
|
+
repo: context.repo.repo,
|
|
70
|
+
issue_number: context.issue.number
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const aiComments = comments.data
|
|
74
|
+
.filter(c => c.body.includes('🤖 AI Code Review'))
|
|
75
|
+
.map(c => ({
|
|
76
|
+
type: 'comment',
|
|
77
|
+
created_at: c.created_at,
|
|
78
|
+
body: c.body
|
|
79
|
+
}));
|
|
80
|
+
|
|
81
|
+
const reviews = await github.rest.pulls.listReviews({
|
|
82
|
+
owner: context.repo.owner,
|
|
83
|
+
repo: context.repo.repo,
|
|
84
|
+
pull_number: context.issue.number
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const aiReviews = reviews.data
|
|
88
|
+
.filter(r => r.body && r.body.includes('🤖 AI Code Review'))
|
|
89
|
+
.map(r => ({
|
|
90
|
+
type: 'review',
|
|
91
|
+
created_at: r.submitted_at,
|
|
92
|
+
state: r.state,
|
|
93
|
+
body: r.body
|
|
94
|
+
}));
|
|
95
|
+
|
|
96
|
+
// Combine and sort by date (newest last)
|
|
97
|
+
const allReviews = [...aiComments, ...aiReviews]
|
|
98
|
+
.sort((a, b) => new Date(a.created_at) - new Date(b.created_at))
|
|
99
|
+
.slice(-3) // Last 3 reviews/comments
|
|
100
|
+
.map(r => r.body)
|
|
101
|
+
.join('\n---\n');
|
|
102
|
+
|
|
103
|
+
const truncated = allReviews.substring(0, 3000);
|
|
104
|
+
|
|
105
|
+
core.setOutput('reviews', truncated);
|
|
106
|
+
core.setOutput('has_reviews', truncated.length > 0 ? 'true' : 'false');
|
|
107
|
+
|
|
108
|
+
- name: Get PR diff
|
|
109
|
+
id: pr-diff
|
|
110
|
+
env:
|
|
111
|
+
BASE_REF: ${{ steps.pr-details.outputs.base_ref }}
|
|
112
|
+
run: |
|
|
113
|
+
git fetch origin "$BASE_REF"
|
|
114
|
+
|
|
115
|
+
DIFF=$(git diff "origin/$BASE_REF...HEAD")
|
|
116
|
+
|
|
117
|
+
# Filter out ignored sections
|
|
118
|
+
echo "$DIFF" | awk '
|
|
119
|
+
/ai-review-ignore-start/ { ignore=1; next }
|
|
120
|
+
/ai-review-ignore-end/ { ignore=0; next }
|
|
121
|
+
!ignore { print }
|
|
122
|
+
' > pr_diff_filtered.txt
|
|
123
|
+
|
|
124
|
+
DIFF=$(cat pr_diff_filtered.txt)
|
|
125
|
+
|
|
126
|
+
DIFF_LENGTH=${#DIFF}
|
|
127
|
+
if [ $DIFF_LENGTH -gt 15000 ]; then
|
|
128
|
+
DIFF="${DIFF:0:15000}"
|
|
129
|
+
DIFF="$DIFF\n\n... (diff truncated due to size)"
|
|
130
|
+
fi
|
|
131
|
+
|
|
132
|
+
echo "$DIFF" > pr_diff.txt
|
|
133
|
+
|
|
134
|
+
- name: Get changed files list
|
|
135
|
+
id: changed-files
|
|
136
|
+
env:
|
|
137
|
+
BASE_REF: ${{ steps.pr-details.outputs.base_ref }}
|
|
138
|
+
run: |
|
|
139
|
+
git fetch origin "$BASE_REF"
|
|
140
|
+
FILES=$(git diff --name-only "origin/$BASE_REF...HEAD" | head -50)
|
|
141
|
+
echo "files<<EOF" >> $GITHUB_OUTPUT
|
|
142
|
+
echo "$FILES" >> $GITHUB_OUTPUT
|
|
143
|
+
echo "EOF" >> $GITHUB_OUTPUT
|
|
144
|
+
|
|
145
|
+
- name: Read project guidelines
|
|
146
|
+
id: guidelines
|
|
147
|
+
run: |
|
|
148
|
+
CLAUDE_MD=""
|
|
149
|
+
RUBOCOP_YML=""
|
|
150
|
+
|
|
151
|
+
# Extract comprehensive sections from CLAUDE.md using shared helper
|
|
152
|
+
if [ -f "CLAUDE.md" ] && [ -f ".ai-reviewer/extract-claude-sections.sh" ]; then
|
|
153
|
+
CLAUDE_MD=$(.ai-reviewer/extract-claude-sections.sh CLAUDE.md)
|
|
154
|
+
fi
|
|
155
|
+
|
|
156
|
+
# Only include key linting rules
|
|
157
|
+
if [ -f ".rubocop.yml" ]; then
|
|
158
|
+
RUBOCOP_YML=$(cat .rubocop.yml | grep -E "^[A-Z]|Enabled:|Max:" | head -c 1000)
|
|
159
|
+
fi
|
|
160
|
+
|
|
161
|
+
echo "claude_md<<EOF" >> $GITHUB_OUTPUT
|
|
162
|
+
echo "$CLAUDE_MD" >> $GITHUB_OUTPUT
|
|
163
|
+
echo "EOF" >> $GITHUB_OUTPUT
|
|
164
|
+
|
|
165
|
+
echo "rubocop_yml<<EOF" >> $GITHUB_OUTPUT
|
|
166
|
+
echo "$RUBOCOP_YML" >> $GITHUB_OUTPUT
|
|
167
|
+
echo "EOF" >> $GITHUB_OUTPUT
|
|
168
|
+
|
|
169
|
+
- name: Extract specific questions from comment
|
|
170
|
+
id: extract-question
|
|
171
|
+
uses: actions/github-script@v8
|
|
172
|
+
with:
|
|
173
|
+
script: |
|
|
174
|
+
const comment = context.payload.comment.body;
|
|
175
|
+
|
|
176
|
+
// Extract any text after the trigger
|
|
177
|
+
const triggers = ['@ai-review', '/review'];
|
|
178
|
+
let question = '';
|
|
179
|
+
|
|
180
|
+
for (const trigger of triggers) {
|
|
181
|
+
if (comment.includes(trigger)) {
|
|
182
|
+
const parts = comment.split(trigger);
|
|
183
|
+
if (parts.length > 1) {
|
|
184
|
+
question = parts[1].trim();
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
core.setOutput('question', question);
|
|
191
|
+
return question;
|
|
192
|
+
|
|
193
|
+
- name: AI Code Review with Codestral
|
|
194
|
+
id: ai-review
|
|
195
|
+
env:
|
|
196
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
197
|
+
PR_TITLE: ${{ steps.pr-details.outputs.pr_title }}
|
|
198
|
+
PR_BODY: ${{ steps.pr-details.outputs.pr_body }}
|
|
199
|
+
CHANGED_FILES: ${{ steps.changed-files.outputs.files }}
|
|
200
|
+
CLAUDE_MD: ${{ steps.guidelines.outputs.claude_md }}
|
|
201
|
+
RUBOCOP_YML: ${{ steps.guidelines.outputs.rubocop_yml }}
|
|
202
|
+
USER_QUESTION: ${{ steps.extract-question.outputs.question }}
|
|
203
|
+
PREVIOUS_REVIEWS: ${{ steps.previous-reviews.outputs.reviews }}
|
|
204
|
+
HAS_PREVIOUS_REVIEWS: ${{ steps.previous-reviews.outputs.has_reviews }}
|
|
205
|
+
run: |
|
|
206
|
+
DIFF=$(cat pr_diff.txt)
|
|
207
|
+
|
|
208
|
+
# Use shared system prompt (source of truth)
|
|
209
|
+
if [ -f ".ai-reviewer/build-system-prompt.sh" ]; then
|
|
210
|
+
.ai-reviewer/build-system-prompt.sh > system.txt
|
|
211
|
+
else
|
|
212
|
+
echo "Error: Shared system prompt script not found" >&2
|
|
213
|
+
exit 1
|
|
214
|
+
fi
|
|
215
|
+
|
|
216
|
+
# Build user prompt
|
|
217
|
+
echo "**PR:** $PR_TITLE" > user.txt
|
|
218
|
+
echo "" >> user.txt
|
|
219
|
+
if [ -n "$PR_BODY" ]; then
|
|
220
|
+
echo "**Desc:** ${PR_BODY:0:500}" >> user.txt
|
|
221
|
+
echo "" >> user.txt
|
|
222
|
+
fi
|
|
223
|
+
|
|
224
|
+
# Add specific question prominently if provided
|
|
225
|
+
if [ -n "$USER_QUESTION" ]; then
|
|
226
|
+
echo "**USER QUESTION:** $USER_QUESTION" >> user.txt
|
|
227
|
+
echo "" >> user.txt
|
|
228
|
+
fi
|
|
229
|
+
|
|
230
|
+
echo "**Files:** $CHANGED_FILES" >> user.txt
|
|
231
|
+
echo "" >> user.txt
|
|
232
|
+
echo "**Diff:**" >> user.txt
|
|
233
|
+
echo '```diff' >> user.txt
|
|
234
|
+
echo "$DIFF" >> user.txt
|
|
235
|
+
echo '```' >> user.txt
|
|
236
|
+
|
|
237
|
+
# Add previous reviews if they exist
|
|
238
|
+
if [ "$HAS_PREVIOUS_REVIEWS" = "true" ]; then
|
|
239
|
+
echo "" >> user.txt
|
|
240
|
+
echo "**Previous Reviews:**" >> user.txt
|
|
241
|
+
echo "$PREVIOUS_REVIEWS" >> user.txt
|
|
242
|
+
echo "" >> user.txt
|
|
243
|
+
echo "Focus on: 1) Were previous issues fixed? 2) New changes since last review" >> user.txt
|
|
244
|
+
fi
|
|
245
|
+
|
|
246
|
+
# Only add guidelines if no previous reviews (first review needs full context)
|
|
247
|
+
if [ "$HAS_PREVIOUS_REVIEWS" != "true" ] && [ -n "$CLAUDE_MD" ]; then
|
|
248
|
+
echo "" >> user.txt
|
|
249
|
+
echo "**Project Guidelines (CLAUDE.md):**" >> user.txt
|
|
250
|
+
echo "$CLAUDE_MD" >> user.txt
|
|
251
|
+
fi
|
|
252
|
+
|
|
253
|
+
# Use jq to build properly escaped JSON from files
|
|
254
|
+
SYSTEM_PROMPT=$(cat system.txt)
|
|
255
|
+
USER_PROMPT=$(cat user.txt)
|
|
256
|
+
|
|
257
|
+
jq -n \
|
|
258
|
+
--arg system "$SYSTEM_PROMPT" \
|
|
259
|
+
--arg user "$USER_PROMPT" \
|
|
260
|
+
'{
|
|
261
|
+
messages: [
|
|
262
|
+
{role: "system", content: $system},
|
|
263
|
+
{role: "user", content: $user}
|
|
264
|
+
],
|
|
265
|
+
model: "Codestral-2501",
|
|
266
|
+
temperature: 0.7,
|
|
267
|
+
max_tokens: 1500,
|
|
268
|
+
top_p: 0.95
|
|
269
|
+
}' > prompt.json
|
|
270
|
+
|
|
271
|
+
# Call GitHub Models API
|
|
272
|
+
RESPONSE=$(curl -s -X POST \
|
|
273
|
+
"https://models.inference.ai.azure.com/chat/completions" \
|
|
274
|
+
-H "Content-Type: application/json" \
|
|
275
|
+
-H "Authorization: Bearer $GITHUB_TOKEN" \
|
|
276
|
+
-d @prompt.json)
|
|
277
|
+
|
|
278
|
+
# Extract review content
|
|
279
|
+
REVIEW=$(echo "$RESPONSE" | jq -r '.choices[0].message.content // "Error: Unable to generate review"')
|
|
280
|
+
|
|
281
|
+
echo "$REVIEW" > review_raw.md
|
|
282
|
+
|
|
283
|
+
if echo "$REVIEW" | grep -q "Error:"; then
|
|
284
|
+
echo "ERROR: Failed to generate review"
|
|
285
|
+
echo "Response: $RESPONSE"
|
|
286
|
+
exit 1
|
|
287
|
+
fi
|
|
288
|
+
|
|
289
|
+
# Smart deduplication - removes both exact duplicates AND similar issues from same file:line
|
|
290
|
+
# This catches cases where AI generates variations like:
|
|
291
|
+
# - "file.rb:10 - Issue A"
|
|
292
|
+
# - "file.rb:10 - Issue B" <- should be deduplicated (same file:line)
|
|
293
|
+
# This allows reviewing config/docs files while preventing repetition
|
|
294
|
+
awk '
|
|
295
|
+
# Extract file:line pattern (e.g., "file.rb:10")
|
|
296
|
+
match($0, /^[[:space:]]*-[[:space:]]*`[^`]+:[0-9]+/) {
|
|
297
|
+
prefix = substr($0, RSTART, RLENGTH)
|
|
298
|
+
# If we have seen this file:line before, skip
|
|
299
|
+
if (seen[prefix]++) next
|
|
300
|
+
}
|
|
301
|
+
# Also skip exact duplicate lines
|
|
302
|
+
!seen_exact[$0]++
|
|
303
|
+
' review_raw.md > review.md
|
|
304
|
+
|
|
305
|
+
# If review is still too long after deduplication, truncate it
|
|
306
|
+
if [ $(wc -l < review.md) -gt 100 ]; then
|
|
307
|
+
head -100 review.md > review_truncated.md
|
|
308
|
+
echo "" >> review_truncated.md
|
|
309
|
+
echo "... (review truncated - too many issues)" >> review_truncated.md
|
|
310
|
+
mv review_truncated.md review.md
|
|
311
|
+
fi
|
|
312
|
+
|
|
313
|
+
- name: Dismiss previous AI reviews
|
|
314
|
+
uses: actions/github-script@v8
|
|
315
|
+
with:
|
|
316
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
317
|
+
script: |
|
|
318
|
+
// Get all existing reviews for this PR
|
|
319
|
+
const reviews = await github.rest.pulls.listReviews({
|
|
320
|
+
owner: context.repo.owner,
|
|
321
|
+
repo: context.repo.repo,
|
|
322
|
+
pull_number: context.issue.number
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
// Find AI reviews that should be dismissed (REQUEST_CHANGES state)
|
|
326
|
+
const aiReviews = reviews.data.filter(r =>
|
|
327
|
+
r.body &&
|
|
328
|
+
r.body.includes('🤖 AI Code Review') &&
|
|
329
|
+
r.state === 'CHANGES_REQUESTED'
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
// Dismiss each AI review
|
|
333
|
+
for (const review of aiReviews) {
|
|
334
|
+
console.log(`Dismissing previous AI review #${review.id}`);
|
|
335
|
+
await github.rest.pulls.dismissReview({
|
|
336
|
+
owner: context.repo.owner,
|
|
337
|
+
repo: context.repo.repo,
|
|
338
|
+
pull_number: context.issue.number,
|
|
339
|
+
review_id: review.id,
|
|
340
|
+
message: 'New AI review requested - dismissing previous AI review'
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
- name: Post review as PR review (not comment)
|
|
345
|
+
uses: actions/github-script@v8
|
|
346
|
+
with:
|
|
347
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
348
|
+
script: |
|
|
349
|
+
const fs = require('fs');
|
|
350
|
+
const review = fs.readFileSync('review.md', 'utf8');
|
|
351
|
+
|
|
352
|
+
// Determine review event type based on the Verdict section only
|
|
353
|
+
// GitHub does not allow bots to APPROVE, so we use COMMENT or REQUEST_CHANGES
|
|
354
|
+
let event = 'COMMENT';
|
|
355
|
+
let verdict = '💬 COMMENT';
|
|
356
|
+
|
|
357
|
+
// Extract only the Verdict section to avoid false positives from policy text
|
|
358
|
+
// Look for the first line after "## Verdict" that contains the status
|
|
359
|
+
const verdictMatch = review.match(/## Verdict\s*\n\*\*Status:\*\*\s*([^\n]*)/i);
|
|
360
|
+
if (verdictMatch) {
|
|
361
|
+
const verdictLine = verdictMatch[1].trim();
|
|
362
|
+
if (verdictLine.includes('REQUEST_CHANGES') || verdictLine.includes('REQUEST CHANGES')) {
|
|
363
|
+
event = 'REQUEST_CHANGES';
|
|
364
|
+
verdict = '⚠️ REQUEST CHANGES';
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const body = `## 🤖 AI Code Review - ${verdict}\n\n${review}\n\n---\n*Powered by GitHub Models (Codestral-2501) • Requested by @${context.payload.comment.user.login} • AI recommendations only, human approval required • [Ignore sections](https://github.com/${context.repo.owner}/${context.repo.repo}/blob/main/docs/AI_PR_REVIEWER.md#ignoring-code-sections) with \`ai-review-ignore-start/end\`*`;
|
|
369
|
+
|
|
370
|
+
// Post as a proper PR review (not an issue comment)
|
|
371
|
+
// This prevents duplication and integrates better with GitHub's review system
|
|
372
|
+
await github.rest.pulls.createReview({
|
|
373
|
+
owner: context.repo.owner,
|
|
374
|
+
repo: context.repo.repo,
|
|
375
|
+
pull_number: context.issue.number,
|
|
376
|
+
body: body,
|
|
377
|
+
event: event
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
- name: Check for critical issues
|
|
381
|
+
run: |
|
|
382
|
+
if grep -qi "REQUEST CHANGES\|CRITICAL\|SECURITY.*CONCERN" review.md; then
|
|
383
|
+
echo "::warning::AI reviewer found critical issues that need attention"
|
|
384
|
+
fi
|