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,328 @@
|
|
|
1
|
+
name: AI PR Review
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
types: [opened, synchronize, reopened]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
pull-requests: write
|
|
10
|
+
issues: write
|
|
11
|
+
models: read
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
ai-review:
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
|
|
17
|
+
steps:
|
|
18
|
+
- name: Checkout code
|
|
19
|
+
uses: actions/checkout@v6
|
|
20
|
+
with:
|
|
21
|
+
fetch-depth: 0
|
|
22
|
+
|
|
23
|
+
- name: Get previous AI reviews and comments
|
|
24
|
+
id: previous-reviews
|
|
25
|
+
uses: actions/github-script@v8
|
|
26
|
+
with:
|
|
27
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
28
|
+
script: |
|
|
29
|
+
// Fetch both PR comments and review comments
|
|
30
|
+
const comments = await github.rest.issues.listComments({
|
|
31
|
+
owner: context.repo.owner,
|
|
32
|
+
repo: context.repo.repo,
|
|
33
|
+
issue_number: context.issue.number
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const aiComments = comments.data
|
|
37
|
+
.filter(c => c.body.includes('🤖 AI Code Review'))
|
|
38
|
+
.map(c => ({
|
|
39
|
+
type: 'comment',
|
|
40
|
+
created_at: c.created_at,
|
|
41
|
+
body: c.body
|
|
42
|
+
}));
|
|
43
|
+
|
|
44
|
+
const reviews = await github.rest.pulls.listReviews({
|
|
45
|
+
owner: context.repo.owner,
|
|
46
|
+
repo: context.repo.repo,
|
|
47
|
+
pull_number: context.issue.number
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const aiReviews = reviews.data
|
|
51
|
+
.filter(r => r.body && r.body.includes('🤖 AI Code Review'))
|
|
52
|
+
.map(r => ({
|
|
53
|
+
type: 'review',
|
|
54
|
+
created_at: r.submitted_at,
|
|
55
|
+
state: r.state,
|
|
56
|
+
body: r.body
|
|
57
|
+
}));
|
|
58
|
+
|
|
59
|
+
// Combine and sort by date (newest last)
|
|
60
|
+
const allReviews = [...aiComments, ...aiReviews]
|
|
61
|
+
.sort((a, b) => new Date(a.created_at) - new Date(b.created_at))
|
|
62
|
+
.slice(-3) // Last 3 reviews/comments
|
|
63
|
+
.map(r => r.body)
|
|
64
|
+
.join('\n---\n');
|
|
65
|
+
|
|
66
|
+
const truncated = allReviews.substring(0, 3000);
|
|
67
|
+
|
|
68
|
+
core.setOutput('reviews', truncated);
|
|
69
|
+
core.setOutput('has_reviews', truncated.length > 0 ? 'true' : 'false');
|
|
70
|
+
|
|
71
|
+
- name: Get PR diff
|
|
72
|
+
id: pr-diff
|
|
73
|
+
env:
|
|
74
|
+
BASE_REF: ${{ github.event.pull_request.base.ref }}
|
|
75
|
+
run: |
|
|
76
|
+
git fetch origin "$BASE_REF"
|
|
77
|
+
DIFF=$(git diff "origin/$BASE_REF...HEAD")
|
|
78
|
+
|
|
79
|
+
# Filter out ignored sections
|
|
80
|
+
# Remove lines between ai-review-ignore-start and ai-review-ignore-end
|
|
81
|
+
echo "$DIFF" | awk '
|
|
82
|
+
/ai-review-ignore-start/ { ignore=1; next }
|
|
83
|
+
/ai-review-ignore-end/ { ignore=0; next }
|
|
84
|
+
!ignore { print }
|
|
85
|
+
' > pr_diff_filtered.txt
|
|
86
|
+
|
|
87
|
+
DIFF=$(cat pr_diff_filtered.txt)
|
|
88
|
+
|
|
89
|
+
DIFF_LENGTH=${#DIFF}
|
|
90
|
+
if [ $DIFF_LENGTH -gt 15000 ]; then
|
|
91
|
+
DIFF="${DIFF:0:15000}"
|
|
92
|
+
DIFF="$DIFF\n\n... (diff truncated due to size)"
|
|
93
|
+
fi
|
|
94
|
+
|
|
95
|
+
echo "$DIFF" > pr_diff.txt
|
|
96
|
+
|
|
97
|
+
- name: Get changed files list
|
|
98
|
+
id: changed-files
|
|
99
|
+
env:
|
|
100
|
+
BASE_REF: ${{ github.event.pull_request.base.ref }}
|
|
101
|
+
run: |
|
|
102
|
+
git fetch origin "$BASE_REF"
|
|
103
|
+
FILES=$(git diff --name-only "origin/$BASE_REF...HEAD" | head -50)
|
|
104
|
+
echo "files<<EOF" >> $GITHUB_OUTPUT
|
|
105
|
+
echo "$FILES" >> $GITHUB_OUTPUT
|
|
106
|
+
echo "EOF" >> $GITHUB_OUTPUT
|
|
107
|
+
|
|
108
|
+
- name: Read project guidelines
|
|
109
|
+
id: guidelines
|
|
110
|
+
run: |
|
|
111
|
+
CLAUDE_MD=""
|
|
112
|
+
|
|
113
|
+
# Extract comprehensive sections from CLAUDE.md using shared helper
|
|
114
|
+
# Only include on first review to save tokens
|
|
115
|
+
if [ -f "CLAUDE.md" ] && [ -f ".ai-reviewer/extract-claude-sections.sh" ]; then
|
|
116
|
+
CLAUDE_MD=$(.ai-reviewer/extract-claude-sections.sh CLAUDE.md)
|
|
117
|
+
fi
|
|
118
|
+
|
|
119
|
+
echo "claude_md<<EOF" >> $GITHUB_OUTPUT
|
|
120
|
+
echo "$CLAUDE_MD" >> $GITHUB_OUTPUT
|
|
121
|
+
echo "EOF" >> $GITHUB_OUTPUT
|
|
122
|
+
|
|
123
|
+
- name: Build prompts
|
|
124
|
+
env:
|
|
125
|
+
PR_TITLE: ${{ github.event.pull_request.title }}
|
|
126
|
+
PR_BODY: ${{ github.event.pull_request.body }}
|
|
127
|
+
CHANGED_FILES: ${{ steps.changed-files.outputs.files }}
|
|
128
|
+
CLAUDE_MD: ${{ steps.guidelines.outputs.claude_md }}
|
|
129
|
+
PREVIOUS_REVIEWS: ${{ steps.previous-reviews.outputs.reviews }}
|
|
130
|
+
HAS_PREVIOUS_REVIEWS: ${{ steps.previous-reviews.outputs.has_reviews }}
|
|
131
|
+
run: |
|
|
132
|
+
DIFF=$(cat pr_diff.txt)
|
|
133
|
+
|
|
134
|
+
# Use shared system prompt (source of truth)
|
|
135
|
+
if [ -f ".ai-reviewer/build-system-prompt.sh" ]; then
|
|
136
|
+
.ai-reviewer/build-system-prompt.sh > system.txt
|
|
137
|
+
else
|
|
138
|
+
echo "Error: Shared system prompt script not found" >&2
|
|
139
|
+
exit 1
|
|
140
|
+
fi
|
|
141
|
+
|
|
142
|
+
# Build user prompt - more concise to save tokens
|
|
143
|
+
echo "**PR:** $PR_TITLE" > user.txt
|
|
144
|
+
echo "" >> user.txt
|
|
145
|
+
|
|
146
|
+
# Add previous reviews if they exist (incremental review mode)
|
|
147
|
+
if [ "$HAS_PREVIOUS_REVIEWS" = "true" ]; then
|
|
148
|
+
echo "**Previous Reviews:**" >> user.txt
|
|
149
|
+
echo "$PREVIOUS_REVIEWS" >> user.txt
|
|
150
|
+
echo "" >> user.txt
|
|
151
|
+
echo "**Focus:** Review new changes only. Were previous issues fixed?" >> user.txt
|
|
152
|
+
echo "" >> user.txt
|
|
153
|
+
fi
|
|
154
|
+
|
|
155
|
+
# Add changed files count (not full list to save tokens)
|
|
156
|
+
FILE_COUNT=$(echo "$CHANGED_FILES" | wc -l)
|
|
157
|
+
echo "**Changed Files ($FILE_COUNT):**" >> user.txt
|
|
158
|
+
echo "$CHANGED_FILES" | head -20 >> user.txt
|
|
159
|
+
if [ $FILE_COUNT -gt 20 ]; then
|
|
160
|
+
echo "... and $((FILE_COUNT - 20)) more" >> user.txt
|
|
161
|
+
fi
|
|
162
|
+
echo "" >> user.txt
|
|
163
|
+
|
|
164
|
+
echo "**Diff:**" >> user.txt
|
|
165
|
+
echo '```diff' >> user.txt
|
|
166
|
+
echo "$DIFF" >> user.txt
|
|
167
|
+
echo '```' >> user.txt
|
|
168
|
+
|
|
169
|
+
# Only add guidelines on first review (token savings)
|
|
170
|
+
if [ "$HAS_PREVIOUS_REVIEWS" != "true" ] && [ -n "$CLAUDE_MD" ]; then
|
|
171
|
+
echo "" >> user.txt
|
|
172
|
+
echo "**Guidelines:**" >> user.txt
|
|
173
|
+
echo "$CLAUDE_MD" >> user.txt
|
|
174
|
+
fi
|
|
175
|
+
|
|
176
|
+
- name: AI Code Review with Codestral
|
|
177
|
+
id: ai-review
|
|
178
|
+
env:
|
|
179
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
180
|
+
run: |
|
|
181
|
+
# Use jq to build properly escaped JSON from files
|
|
182
|
+
SYSTEM_PROMPT=$(cat system.txt)
|
|
183
|
+
USER_PROMPT=$(cat user.txt)
|
|
184
|
+
|
|
185
|
+
# Reduce max_tokens to save costs (was 1500, now 1000)
|
|
186
|
+
# Increased temperature slightly for more concise responses
|
|
187
|
+
jq -n \
|
|
188
|
+
--arg system "$SYSTEM_PROMPT" \
|
|
189
|
+
--arg user "$USER_PROMPT" \
|
|
190
|
+
'{
|
|
191
|
+
messages: [
|
|
192
|
+
{role: "system", content: $system},
|
|
193
|
+
{role: "user", content: $user}
|
|
194
|
+
],
|
|
195
|
+
model: "Codestral-2501",
|
|
196
|
+
temperature: 0.6,
|
|
197
|
+
max_tokens: 1000,
|
|
198
|
+
top_p: 0.9
|
|
199
|
+
}' > prompt.json
|
|
200
|
+
|
|
201
|
+
# Call GitHub Models API
|
|
202
|
+
RESPONSE=$(curl -s -X POST \
|
|
203
|
+
"https://models.inference.ai.azure.com/chat/completions" \
|
|
204
|
+
-H "Content-Type: application/json" \
|
|
205
|
+
-H "Authorization: Bearer $GITHUB_TOKEN" \
|
|
206
|
+
-d @prompt.json)
|
|
207
|
+
|
|
208
|
+
# Extract review content
|
|
209
|
+
REVIEW=$(echo "$RESPONSE" | jq -r '.choices[0].message.content // "Error: Unable to generate review"')
|
|
210
|
+
|
|
211
|
+
echo "$REVIEW" > review_raw.md
|
|
212
|
+
|
|
213
|
+
if echo "$REVIEW" | grep -q "Error:"; then
|
|
214
|
+
echo "ERROR: Failed to generate review"
|
|
215
|
+
echo "Response: $RESPONSE"
|
|
216
|
+
exit 1
|
|
217
|
+
fi
|
|
218
|
+
|
|
219
|
+
# Smart deduplication - removes both exact duplicates AND similar issues from same file:line
|
|
220
|
+
# This catches cases where AI generates variations like:
|
|
221
|
+
# - "file.rb:10 - Issue A"
|
|
222
|
+
# - "file.rb:10 - Issue B" <- should be deduplicated (same file:line)
|
|
223
|
+
# This allows reviewing config/docs files while preventing repetition
|
|
224
|
+
awk '
|
|
225
|
+
# Extract file:line pattern (e.g., "file.rb:10")
|
|
226
|
+
match($0, /^[[:space:]]*-[[:space:]]*`[^`]+:[0-9]+/) {
|
|
227
|
+
prefix = substr($0, RSTART, RLENGTH)
|
|
228
|
+
# If we have seen this file:line before, skip
|
|
229
|
+
if (seen[prefix]++) next
|
|
230
|
+
}
|
|
231
|
+
# Also skip exact duplicate lines
|
|
232
|
+
!seen_exact[$0]++
|
|
233
|
+
' review_raw.md > review.md
|
|
234
|
+
|
|
235
|
+
# If review is still too long after deduplication, truncate it
|
|
236
|
+
if [ $(wc -l < review.md) -gt 100 ]; then
|
|
237
|
+
head -100 review.md > review_truncated.md
|
|
238
|
+
echo "" >> review_truncated.md
|
|
239
|
+
echo "... (review truncated - too many issues)" >> review_truncated.md
|
|
240
|
+
mv review_truncated.md review.md
|
|
241
|
+
fi
|
|
242
|
+
|
|
243
|
+
- name: Dismiss previous AI reviews
|
|
244
|
+
uses: actions/github-script@v8
|
|
245
|
+
with:
|
|
246
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
247
|
+
script: |
|
|
248
|
+
// Get all existing reviews for this PR
|
|
249
|
+
const reviews = await github.rest.pulls.listReviews({
|
|
250
|
+
owner: context.repo.owner,
|
|
251
|
+
repo: context.repo.repo,
|
|
252
|
+
pull_number: context.issue.number
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// Find AI reviews that should be dismissed (REQUEST_CHANGES state)
|
|
256
|
+
const aiReviews = reviews.data.filter(r =>
|
|
257
|
+
r.body &&
|
|
258
|
+
r.body.includes('🤖 AI Code Review') &&
|
|
259
|
+
r.state === 'CHANGES_REQUESTED'
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
// Dismiss each AI review
|
|
263
|
+
for (const review of aiReviews) {
|
|
264
|
+
console.log(`Dismissing previous AI review #${review.id}`);
|
|
265
|
+
await github.rest.pulls.dismissReview({
|
|
266
|
+
owner: context.repo.owner,
|
|
267
|
+
repo: context.repo.repo,
|
|
268
|
+
pull_number: context.issue.number,
|
|
269
|
+
review_id: review.id,
|
|
270
|
+
message: 'New changes have been pushed - dismissing previous AI review'
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
- name: Post review as PR review (not comment)
|
|
275
|
+
uses: actions/github-script@v8
|
|
276
|
+
with:
|
|
277
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
278
|
+
script: |
|
|
279
|
+
const fs = require('fs');
|
|
280
|
+
const review = fs.readFileSync('review.md', 'utf8');
|
|
281
|
+
|
|
282
|
+
// Determine review event type based on the Verdict section only
|
|
283
|
+
// GitHub does not allow bots to APPROVE, so we use COMMENT or REQUEST_CHANGES
|
|
284
|
+
let event = 'COMMENT';
|
|
285
|
+
let verdict = '💬 COMMENT';
|
|
286
|
+
|
|
287
|
+
// Extract only the Verdict section to avoid false positives from policy text
|
|
288
|
+
// Look for the first line after "## Verdict" that contains the status
|
|
289
|
+
const verdictMatch = review.match(/## Verdict\s*\n\*\*Status:\*\*\s*([^\n]*)/i);
|
|
290
|
+
if (verdictMatch) {
|
|
291
|
+
const verdictLine = verdictMatch[1].trim();
|
|
292
|
+
if (verdictLine.includes('REQUEST_CHANGES') || verdictLine.includes('REQUEST CHANGES')) {
|
|
293
|
+
event = 'REQUEST_CHANGES';
|
|
294
|
+
verdict = '⚠️ REQUEST CHANGES';
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const body = `## 🤖 AI Code Review - ${verdict}\n\n${review}\n\n---\n*Powered by GitHub Models (Codestral-2501) • 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\`*`;
|
|
299
|
+
|
|
300
|
+
// Post as a proper PR review (not an issue comment)
|
|
301
|
+
// This prevents duplication and integrates better with GitHub's review system
|
|
302
|
+
await github.rest.pulls.createReview({
|
|
303
|
+
owner: context.repo.owner,
|
|
304
|
+
repo: context.repo.repo,
|
|
305
|
+
pull_number: context.issue.number,
|
|
306
|
+
body: body,
|
|
307
|
+
event: event
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
- name: Check for critical issues
|
|
311
|
+
run: |
|
|
312
|
+
if grep -qi "REQUEST CHANGES\|CRITICAL\|SECURITY.*CONCERN" review.md; then
|
|
313
|
+
echo "::warning::AI reviewer found critical issues that need attention"
|
|
314
|
+
fi
|
|
315
|
+
|
|
316
|
+
# Alternative models (commented out - uncomment to switch):
|
|
317
|
+
#
|
|
318
|
+
# For Mistral Large:
|
|
319
|
+
# Replace "Codestral-2501" with "Mistral-large-2411"
|
|
320
|
+
# Model: https://github.com/marketplace/models/azureml-mistral/Mistral-large-2411
|
|
321
|
+
#
|
|
322
|
+
# For Mistral Small:
|
|
323
|
+
# Replace "Codestral-2501" with "Mistral-small"
|
|
324
|
+
# Model: https://github.com/marketplace/models/azureml-mistral/Mistral-small
|
|
325
|
+
#
|
|
326
|
+
# For Mistral Nemo:
|
|
327
|
+
# Replace "Codestral-2501" with "Mistral-Nemo"
|
|
328
|
+
# Model: https://github.com/marketplace/models/azureml-mistral/Mistral-Nemo
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
name: License Check
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [ main ]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [ main ]
|
|
8
|
+
schedule:
|
|
9
|
+
- cron: '0 8 * * 1' # Weekly on Monday at 8am UTC (after dependency updates)
|
|
10
|
+
workflow_dispatch: # Allow manual triggering
|
|
11
|
+
|
|
12
|
+
permissions:
|
|
13
|
+
contents: read
|
|
14
|
+
actions: read
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
license-check:
|
|
18
|
+
name: Gem License Compliance
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
steps:
|
|
21
|
+
- name: Checkout
|
|
22
|
+
uses: actions/checkout@v6
|
|
23
|
+
|
|
24
|
+
- name: Setup Ruby
|
|
25
|
+
uses: ruby/setup-ruby@v1
|
|
26
|
+
with:
|
|
27
|
+
ruby-version: 3.4.7
|
|
28
|
+
bundler-cache: true
|
|
29
|
+
|
|
30
|
+
- name: Make license check script executable
|
|
31
|
+
run: chmod +x scripts/license-check.rb
|
|
32
|
+
|
|
33
|
+
- name: Run License Check
|
|
34
|
+
run: ruby scripts/license-check.rb
|
|
35
|
+
|
|
36
|
+
- name: Upload license report as artifact
|
|
37
|
+
uses: actions/upload-artifact@v6
|
|
38
|
+
if: failure()
|
|
39
|
+
with:
|
|
40
|
+
name: license-check-report
|
|
41
|
+
path: |
|
|
42
|
+
Gemfile.lock
|
|
43
|
+
config/license_overrides.yml
|
|
44
|
+
retention-days: 30
|
|
45
|
+
if-no-files-found: ignore
|
|
46
|
+
|
|
47
|
+
license-audit:
|
|
48
|
+
name: License Audit (Monthly)
|
|
49
|
+
runs-on: ubuntu-latest
|
|
50
|
+
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
|
|
51
|
+
steps:
|
|
52
|
+
- name: Checkout
|
|
53
|
+
uses: actions/checkout@v6
|
|
54
|
+
|
|
55
|
+
- name: Setup Ruby
|
|
56
|
+
uses: ruby/setup-ruby@v1
|
|
57
|
+
with:
|
|
58
|
+
ruby-version: 3.4.7
|
|
59
|
+
bundler-cache: true
|
|
60
|
+
|
|
61
|
+
- name: Install license_finder
|
|
62
|
+
run: gem install license_finder
|
|
63
|
+
|
|
64
|
+
- name: Generate detailed license report
|
|
65
|
+
run: |
|
|
66
|
+
echo "# License Audit Report" > license-audit-report.md
|
|
67
|
+
echo "Generated on: $(date)" >> license-audit-report.md
|
|
68
|
+
echo "" >> license-audit-report.md
|
|
69
|
+
echo "## All Gem Licenses" >> license-audit-report.md
|
|
70
|
+
echo "" >> license-audit-report.md
|
|
71
|
+
license_finder --format markdown >> license-audit-report.md
|
|
72
|
+
|
|
73
|
+
- name: Upload detailed license audit
|
|
74
|
+
uses: actions/upload-artifact@v6
|
|
75
|
+
with:
|
|
76
|
+
name: license-audit-report
|
|
77
|
+
path: license-audit-report.md
|
|
78
|
+
retention-days: 90
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
name: Lint
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [ main, master ]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [ main, master ]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
rubocop:
|
|
11
|
+
name: RuboCop (Ruby Linting)
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
|
|
14
|
+
steps:
|
|
15
|
+
- name: Checkout code
|
|
16
|
+
uses: actions/checkout@v6
|
|
17
|
+
|
|
18
|
+
- name: Set up Ruby
|
|
19
|
+
uses: ruby/setup-ruby@v1
|
|
20
|
+
with:
|
|
21
|
+
ruby-version: '3.4'
|
|
22
|
+
bundler-cache: true
|
|
23
|
+
|
|
24
|
+
- name: Run RuboCop
|
|
25
|
+
run: bundle exec rubocop --parallel
|
|
26
|
+
|
|
27
|
+
stylelint:
|
|
28
|
+
name: Stylelint (CSS Linting)
|
|
29
|
+
runs-on: ubuntu-latest
|
|
30
|
+
|
|
31
|
+
steps:
|
|
32
|
+
- name: Checkout code
|
|
33
|
+
uses: actions/checkout@v6
|
|
34
|
+
|
|
35
|
+
- name: Set up Node.js
|
|
36
|
+
uses: actions/setup-node@v6
|
|
37
|
+
with:
|
|
38
|
+
node-version: '20'
|
|
39
|
+
cache: 'yarn'
|
|
40
|
+
|
|
41
|
+
- name: Install dependencies
|
|
42
|
+
run: yarn install --frozen-lockfile
|
|
43
|
+
|
|
44
|
+
- name: Run Stylelint
|
|
45
|
+
run: yarn lint:css
|
|
46
|
+
|
|
47
|
+
brakeman:
|
|
48
|
+
name: Brakeman (Security Scan)
|
|
49
|
+
runs-on: ubuntu-latest
|
|
50
|
+
|
|
51
|
+
steps:
|
|
52
|
+
- name: Checkout code
|
|
53
|
+
uses: actions/checkout@v6
|
|
54
|
+
|
|
55
|
+
- name: Set up Ruby
|
|
56
|
+
uses: ruby/setup-ruby@v1
|
|
57
|
+
with:
|
|
58
|
+
ruby-version: '3.4'
|
|
59
|
+
bundler-cache: true
|
|
60
|
+
|
|
61
|
+
- name: Run Brakeman
|
|
62
|
+
run: bundle exec brakeman --quiet --no-pager
|
|
63
|
+
|
|
64
|
+
bundle-audit:
|
|
65
|
+
name: Bundle Audit (Dependency Security)
|
|
66
|
+
runs-on: ubuntu-latest
|
|
67
|
+
|
|
68
|
+
steps:
|
|
69
|
+
- name: Checkout code
|
|
70
|
+
uses: actions/checkout@v6
|
|
71
|
+
|
|
72
|
+
- name: Set up Ruby
|
|
73
|
+
uses: ruby/setup-ruby@v1
|
|
74
|
+
with:
|
|
75
|
+
ruby-version: '3.4'
|
|
76
|
+
bundler-cache: true
|
|
77
|
+
|
|
78
|
+
- name: Run Bundle Audit
|
|
79
|
+
run: bundle exec bundle-audit check --update
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
name: Security Scanning
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [ main, develop ]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [ main ]
|
|
8
|
+
schedule:
|
|
9
|
+
- cron: '0 6 * * 1' # Weekly on Monday at 6am UTC
|
|
10
|
+
workflow_dispatch: # Allow manual triggering
|
|
11
|
+
|
|
12
|
+
permissions:
|
|
13
|
+
contents: read
|
|
14
|
+
security-events: write
|
|
15
|
+
actions: read
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
trufflehog:
|
|
19
|
+
name: TruffleHog Secret Scan
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
steps:
|
|
22
|
+
- name: Checkout
|
|
23
|
+
uses: actions/checkout@v6
|
|
24
|
+
with:
|
|
25
|
+
fetch-depth: 0
|
|
26
|
+
|
|
27
|
+
- name: TruffleHog OSS
|
|
28
|
+
uses: trufflesecurity/trufflehog@main
|
|
29
|
+
with:
|
|
30
|
+
path: ./
|
|
31
|
+
base: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || 'HEAD~1' }}
|
|
32
|
+
head: HEAD
|
|
33
|
+
extra_args: --debug --only-verified
|
|
34
|
+
continue-on-error: false
|
|
35
|
+
|
|
36
|
+
semgrep:
|
|
37
|
+
name: Semgrep Security Analysis
|
|
38
|
+
runs-on: ubuntu-latest
|
|
39
|
+
if: (github.event_name == 'push' && github.ref == 'refs/heads/main') || github.event_name == 'pull_request'
|
|
40
|
+
container:
|
|
41
|
+
image: semgrep/semgrep
|
|
42
|
+
steps:
|
|
43
|
+
- name: Checkout
|
|
44
|
+
uses: actions/checkout@v6
|
|
45
|
+
|
|
46
|
+
- name: Run Semgrep
|
|
47
|
+
env:
|
|
48
|
+
SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}
|
|
49
|
+
run: |
|
|
50
|
+
if [ -n "$SEMGREP_APP_TOKEN" ]; then
|
|
51
|
+
echo "Running semgrep ci with token"
|
|
52
|
+
semgrep ci --json --output=semgrep-results.json || true
|
|
53
|
+
else
|
|
54
|
+
echo "Running semgrep with auto config (no token)"
|
|
55
|
+
semgrep --config=auto \
|
|
56
|
+
--exclude="*.min.js" \
|
|
57
|
+
--exclude="node_modules" \
|
|
58
|
+
--exclude="vendor" \
|
|
59
|
+
--exclude="tmp" \
|
|
60
|
+
--exclude="log" \
|
|
61
|
+
--exclude="public/assets" \
|
|
62
|
+
--json --output=semgrep-results.json . || true
|
|
63
|
+
fi
|
|
64
|
+
continue-on-error: false
|
|
65
|
+
|
|
66
|
+
- name: Upload Semgrep Results
|
|
67
|
+
uses: actions/upload-artifact@v6
|
|
68
|
+
if: always()
|
|
69
|
+
with:
|
|
70
|
+
name: semgrep-results
|
|
71
|
+
path: semgrep-results.json
|
|
72
|
+
retention-days: 30
|
|
73
|
+
|
|
74
|
+
semgrep-manual:
|
|
75
|
+
name: Manual Semgrep Trigger
|
|
76
|
+
runs-on: ubuntu-latest
|
|
77
|
+
if: github.event_name == 'workflow_dispatch'
|
|
78
|
+
container:
|
|
79
|
+
image: semgrep/semgrep
|
|
80
|
+
steps:
|
|
81
|
+
- name: Checkout
|
|
82
|
+
uses: actions/checkout@v6
|
|
83
|
+
|
|
84
|
+
- name: Run Semgrep (Manual)
|
|
85
|
+
env:
|
|
86
|
+
SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}
|
|
87
|
+
run: semgrep ci
|
|
88
|
+
continue-on-error: false
|
|
89
|
+
|
|
90
|
+
dependency-check:
|
|
91
|
+
name: Dependency Vulnerability Scan
|
|
92
|
+
runs-on: ubuntu-latest
|
|
93
|
+
steps:
|
|
94
|
+
- name: Checkout
|
|
95
|
+
uses: actions/checkout@v6
|
|
96
|
+
|
|
97
|
+
- name: Setup Ruby
|
|
98
|
+
uses: ruby/setup-ruby@v1
|
|
99
|
+
with:
|
|
100
|
+
ruby-version: 3.4.7
|
|
101
|
+
bundler-cache: true
|
|
102
|
+
|
|
103
|
+
- name: Bundle Audit
|
|
104
|
+
run: |
|
|
105
|
+
gem install bundler-audit > /dev/null 2>&1
|
|
106
|
+
bundle-audit check --update
|
|
107
|
+
|
|
108
|
+
docker-security:
|
|
109
|
+
name: Docker Security Scan
|
|
110
|
+
runs-on: ubuntu-latest
|
|
111
|
+
steps:
|
|
112
|
+
- name: Checkout
|
|
113
|
+
uses: actions/checkout@v6
|
|
114
|
+
|
|
115
|
+
- name: Run Trivy config scanner on Dockerfile
|
|
116
|
+
uses: aquasecurity/trivy-action@master
|
|
117
|
+
with:
|
|
118
|
+
scan-type: 'config'
|
|
119
|
+
scan-ref: './Dockerfile'
|
|
120
|
+
format: 'sarif'
|
|
121
|
+
output: 'trivy-results.sarif'
|
|
122
|
+
continue-on-error: true
|
|
123
|
+
|
|
124
|
+
- name: Upload Trivy scan results as artifacts
|
|
125
|
+
uses: actions/upload-artifact@v6
|
|
126
|
+
if: always()
|
|
127
|
+
with:
|
|
128
|
+
name: trivy-security-results
|
|
129
|
+
path: trivy-results.sarif
|
|
130
|
+
retention-days: 30
|
|
131
|
+
if-no-files-found: ignore
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
on:
|
|
2
|
+
workflow_dispatch: {}
|
|
3
|
+
pull_request: {}
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
- master
|
|
8
|
+
paths:
|
|
9
|
+
- .github/workflows/semgrep.yml
|
|
10
|
+
schedule:
|
|
11
|
+
# random HH:MM to avoid a load spike on GitHub Actions at 00:00
|
|
12
|
+
- cron: 1 14 * * *
|
|
13
|
+
name: Semgrep
|
|
14
|
+
jobs:
|
|
15
|
+
semgrep:
|
|
16
|
+
name: semgrep/ci
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
permissions:
|
|
19
|
+
contents: read
|
|
20
|
+
env:
|
|
21
|
+
SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}
|
|
22
|
+
container:
|
|
23
|
+
image: semgrep/semgrep
|
|
24
|
+
steps:
|
|
25
|
+
- uses: actions/checkout@v6
|
|
26
|
+
- run: semgrep ci
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
name: Test with Coverage
|
|
2
|
+
|
|
3
|
+
on: [push, pull_request]
|
|
4
|
+
|
|
5
|
+
jobs:
|
|
6
|
+
test:
|
|
7
|
+
runs-on: ubuntu-latest
|
|
8
|
+
strategy:
|
|
9
|
+
matrix:
|
|
10
|
+
ruby-version: [3.4.7]
|
|
11
|
+
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v6
|
|
14
|
+
|
|
15
|
+
- name: Set up Ruby
|
|
16
|
+
uses: ruby/setup-ruby@v1
|
|
17
|
+
with:
|
|
18
|
+
ruby-version: ${{ matrix.ruby-version }}
|
|
19
|
+
bundler-cache: true
|
|
20
|
+
|
|
21
|
+
- name: Install dependencies
|
|
22
|
+
run: |
|
|
23
|
+
gem install bundler
|
|
24
|
+
bundle install --jobs 4 --retry 3
|
|
25
|
+
|
|
26
|
+
- name: Run tests
|
|
27
|
+
run: |
|
|
28
|
+
bundle exec rake test
|
|
29
|
+
|
|
30
|
+
- name: Check test coverage
|
|
31
|
+
run: |
|
|
32
|
+
bundle exec rake coverage
|
|
33
|
+
# Verify coverage is at least 80%
|
|
34
|
+
if [ ! -f coverage/.last_run.json ]; then
|
|
35
|
+
echo "Coverage report not found"
|
|
36
|
+
exit 1
|
|
37
|
+
fi
|
|
38
|
+
coverage=$(grep -o '"covered_percent":[0-9.]*' coverage/.last_run.json | cut -d':' -f2)
|
|
39
|
+
if (( $(echo "$coverage < 80.0" | bc -l) )); then
|
|
40
|
+
echo "Coverage is below 80%: $coverage%"
|
|
41
|
+
exit 1
|
|
42
|
+
else
|
|
43
|
+
echo "Coverage is at least 80%: $coverage%"
|
|
44
|
+
fi
|