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.
Files changed (251) hide show
  1. checksums.yaml +7 -0
  2. data/.ai-reviewer/README.md +182 -0
  3. data/.ai-reviewer/ai-reviewer.sh +56 -0
  4. data/.ai-reviewer/build-system-prompt.sh +136 -0
  5. data/.ai-reviewer/extract-claude-sections.sh +32 -0
  6. data/.ai-reviewer/test-ai-reviewer.sh +40 -0
  7. data/.ai-reviewer-config.yml +190 -0
  8. data/.github/dependabot.yml +12 -0
  9. data/.github/settings.yml +70 -0
  10. data/.github/workflows/ai-pr-review-on-comment.yml +384 -0
  11. data/.github/workflows/ai-pr-review.yml +328 -0
  12. data/.github/workflows/license-check.yml +78 -0
  13. data/.github/workflows/lint.yml +79 -0
  14. data/.github/workflows/security.yml +131 -0
  15. data/.github/workflows/semgrep.yml +26 -0
  16. data/.github/workflows/test.yml +44 -0
  17. data/.gitignore +75 -0
  18. data/.rubocop.yml +33 -0
  19. data/.ruby-version +1 -0
  20. data/.simplecov +14 -0
  21. data/.stylelintignore +10 -0
  22. data/.stylelintrc.json +37 -0
  23. data/AGENTS.md +51 -0
  24. data/CHANGELOG.md +568 -0
  25. data/CLAUDE.md +632 -0
  26. data/Gemfile +8 -0
  27. data/Gemfile.lock +327 -0
  28. data/LICENSE +21 -0
  29. data/README.md +1209 -0
  30. data/Rakefile +25 -0
  31. data/app/assets/images/cherry_blossom.svg +1525 -0
  32. data/app/assets/images/cherry_blossom_tree.png +0 -0
  33. data/app/assets/images/prysm-icon.png +0 -0
  34. data/app/assets/stylesheets/base.css +29 -0
  35. data/app/assets/stylesheets/components.css +1652 -0
  36. data/app/assets/stylesheets/forms.css +152 -0
  37. data/app/assets/stylesheets/loading.css +145 -0
  38. data/app/assets/stylesheets/neon_sakura.css +40 -0
  39. data/app/assets/stylesheets/pagy-tailwind.css +120 -0
  40. data/app/assets/stylesheets/theme-default.css +40 -0
  41. data/app/assets/stylesheets/theme-green.css +84 -0
  42. data/app/assets/stylesheets/theme-purple.css +94 -0
  43. data/app/assets/stylesheets/theme-red.css +84 -0
  44. data/app/assets/stylesheets/utility-borders.css +29 -0
  45. data/app/assets/stylesheets/utility-colors.css +185 -0
  46. data/app/assets/stylesheets/utility-effects.css +123 -0
  47. data/app/assets/stylesheets/utility-gradients.css +158 -0
  48. data/app/assets/stylesheets/utility-layout.css +132 -0
  49. data/app/assets/stylesheets/utility-reset.css +13 -0
  50. data/app/assets/stylesheets/utility-responsive.css +145 -0
  51. data/app/assets/stylesheets/utility-sizing.css +99 -0
  52. data/app/assets/stylesheets/utility-spacing.css +174 -0
  53. data/app/assets/stylesheets/utility-typography.css +97 -0
  54. data/app/controllers/errors_controller.rb +120 -0
  55. data/app/controllers/style_guide_controller.rb +117 -0
  56. data/app/helpers/errors_helper.rb +12 -0
  57. data/app/helpers/neon_sakura/navbar_helper.rb +43 -0
  58. data/app/helpers/style_guide_helper.rb +36 -0
  59. data/app/javascript/neon_sakura/dropdown.js +22 -0
  60. data/app/javascript/neon_sakura/navbar.js +71 -0
  61. data/app/javascript/neon_sakura/theme_switcher.js +187 -0
  62. data/app/views/errors/show.html.erb +105 -0
  63. data/app/views/layouts/error.html.erb +19 -0
  64. data/app/views/layouts/mission_control/jobs/_application_selection.html.erb +14 -0
  65. data/app/views/layouts/mission_control/jobs/_navigation.html.erb +21 -0
  66. data/app/views/layouts/mission_control/jobs/application.html.erb +453 -0
  67. data/app/views/layouts/style_guide.html.erb +416 -0
  68. data/app/views/shared/_file_upload.html.erb +184 -0
  69. data/app/views/shared/_footer.html.erb +23 -0
  70. data/app/views/shared/_header.html.erb +42 -0
  71. data/app/views/shared/_navbar.html.erb +306 -0
  72. data/app/views/shared/_profile_image_selector.html.erb +165 -0
  73. data/app/views/shared/_theme_switcher.html.erb +64 -0
  74. data/app/views/shared/icons/_adjustments.html.erb +10 -0
  75. data/app/views/shared/icons/_alert_circle.html.erb +3 -0
  76. data/app/views/shared/icons/_alert_triangle.html.erb +3 -0
  77. data/app/views/shared/icons/_archive.html.erb +3 -0
  78. data/app/views/shared/icons/_arrow_down.html.erb +3 -0
  79. data/app/views/shared/icons/_arrow_left.html.erb +3 -0
  80. data/app/views/shared/icons/_arrow_up.html.erb +3 -0
  81. data/app/views/shared/icons/_arrows_pointing_in.html.erb +10 -0
  82. data/app/views/shared/icons/_arrows_pointing_out.html.erb +10 -0
  83. data/app/views/shared/icons/_artemis_logo.html.erb +26 -0
  84. data/app/views/shared/icons/_auth_banner.html.erb +1 -0
  85. data/app/views/shared/icons/_bars.html.erb +10 -0
  86. data/app/views/shared/icons/_bell.html.erb +3 -0
  87. data/app/views/shared/icons/_book.html.erb +3 -0
  88. data/app/views/shared/icons/_bookmark.html.erb +3 -0
  89. data/app/views/shared/icons/_box.html.erb +3 -0
  90. data/app/views/shared/icons/_brain.html.erb +3 -0
  91. data/app/views/shared/icons/_briefcase.html.erb +3 -0
  92. data/app/views/shared/icons/_calendar.html.erb +3 -0
  93. data/app/views/shared/icons/_camera.html.erb +4 -0
  94. data/app/views/shared/icons/_chart_bar.html.erb +3 -0
  95. data/app/views/shared/icons/_chart_line.html.erb +10 -0
  96. data/app/views/shared/icons/_chart_pie.html.erb +11 -0
  97. data/app/views/shared/icons/_chat.html.erb +3 -0
  98. data/app/views/shared/icons/_check.html.erb +3 -0
  99. data/app/views/shared/icons/_check_circle.html.erb +3 -0
  100. data/app/views/shared/icons/_cherry_blossom.html.erb +1516 -0
  101. data/app/views/shared/icons/_cherry_blossom_silhouette.html.erb +1016 -0
  102. data/app/views/shared/icons/_cherry_blossom_single_flower.html.erb +1125 -0
  103. data/app/views/shared/icons/_cherry_blossom_tree.html.erb +159 -0
  104. data/app/views/shared/icons/_chevron_down.html.erb +3 -0
  105. data/app/views/shared/icons/_chevron_right.html.erb +9 -0
  106. data/app/views/shared/icons/_clipboard.html.erb +3 -0
  107. data/app/views/shared/icons/_clock.html.erb +3 -0
  108. data/app/views/shared/icons/_close.html.erb +3 -0
  109. data/app/views/shared/icons/_cog.html.erb +4 -0
  110. data/app/views/shared/icons/_crop.html.erb +10 -0
  111. data/app/views/shared/icons/_crown.html.erb +3 -0
  112. data/app/views/shared/icons/_disc.html.erb +3 -0
  113. data/app/views/shared/icons/_download.html.erb +3 -0
  114. data/app/views/shared/icons/_dragonfly.html.erb +58 -0
  115. data/app/views/shared/icons/_duplicate.html.erb +4 -0
  116. data/app/views/shared/icons/_edit.html.erb +3 -0
  117. data/app/views/shared/icons/_envelope.html.erb +3 -0
  118. data/app/views/shared/icons/_eraser.html.erb +10 -0
  119. data/app/views/shared/icons/_external_link.html.erb +3 -0
  120. data/app/views/shared/icons/_eye.html.erb +4 -0
  121. data/app/views/shared/icons/_file_csv.html.erb +10 -0
  122. data/app/views/shared/icons/_file_export.html.erb +10 -0
  123. data/app/views/shared/icons/_file_image.html.erb +10 -0
  124. data/app/views/shared/icons/_file_import.html.erb +10 -0
  125. data/app/views/shared/icons/_file_question.html.erb +6 -0
  126. data/app/views/shared/icons/_film.html.erb +3 -0
  127. data/app/views/shared/icons/_filter.html.erb +3 -0
  128. data/app/views/shared/icons/_folder.html.erb +3 -0
  129. data/app/views/shared/icons/_folder_open.html.erb +3 -0
  130. data/app/views/shared/icons/_folder_plus.html.erb +3 -0
  131. data/app/views/shared/icons/_globe.html.erb +3 -0
  132. data/app/views/shared/icons/_google.html.erb +11 -0
  133. data/app/views/shared/icons/_heart.html.erb +3 -0
  134. data/app/views/shared/icons/_heart_broken.html.erb +11 -0
  135. data/app/views/shared/icons/_heart_pulse.html.erb +4 -0
  136. data/app/views/shared/icons/_history.html.erb +11 -0
  137. data/app/views/shared/icons/_home.html.erb +10 -0
  138. data/app/views/shared/icons/_image.html.erb +3 -0
  139. data/app/views/shared/icons/_inbox.html.erb +3 -0
  140. data/app/views/shared/icons/_info_circle.html.erb +10 -0
  141. data/app/views/shared/icons/_key.html.erb +3 -0
  142. data/app/views/shared/icons/_layers.html.erb +10 -0
  143. data/app/views/shared/icons/_lightbulb.html.erb +10 -0
  144. data/app/views/shared/icons/_lightning.html.erb +3 -0
  145. data/app/views/shared/icons/_list.html.erb +3 -0
  146. data/app/views/shared/icons/_lock.html.erb +3 -0
  147. data/app/views/shared/icons/_logout.html.erb +3 -0
  148. data/app/views/shared/icons/_magazine.html.erb +3 -0
  149. data/app/views/shared/icons/_magic.html.erb +3 -0
  150. data/app/views/shared/icons/_minus.html.erb +10 -0
  151. data/app/views/shared/icons/_mobile.html.erb +10 -0
  152. data/app/views/shared/icons/_moon.html.erb +3 -0
  153. data/app/views/shared/icons/_network.html.erb +10 -0
  154. data/app/views/shared/icons/_new_item_banner.html.erb +1 -0
  155. data/app/views/shared/icons/_ouroboros.html.erb +24 -0
  156. data/app/views/shared/icons/_package.html.erb +3 -0
  157. data/app/views/shared/icons/_palette.html.erb +3 -0
  158. data/app/views/shared/icons/_paper_plane.html.erb +10 -0
  159. data/app/views/shared/icons/_photo.html.erb +10 -0
  160. data/app/views/shared/icons/_play.html.erb +4 -0
  161. data/app/views/shared/icons/_plus.html.erb +3 -0
  162. data/app/views/shared/icons/_pocket.html.erb +11 -0
  163. data/app/views/shared/icons/_prysm-icon.html.erb +34 -0
  164. data/app/views/shared/icons/_prysm.html.erb +13 -0
  165. data/app/views/shared/icons/_pushbullet-1.html.erb +29 -0
  166. data/app/views/shared/icons/_pushbullet-2.html.erb +2 -0
  167. data/app/views/shared/icons/_puzzle.html.erb +10 -0
  168. data/app/views/shared/icons/_qrcode.html.erb +3 -0
  169. data/app/views/shared/icons/_question.html.erb +3 -0
  170. data/app/views/shared/icons/_receipt.html.erb +10 -0
  171. data/app/views/shared/icons/_redo.html.erb +3 -0
  172. data/app/views/shared/icons/_refresh.html.erb +3 -0
  173. data/app/views/shared/icons/_rocket.html.erb +10 -0
  174. data/app/views/shared/icons/_rss.html.erb +3 -0
  175. data/app/views/shared/icons/_save.html.erb +3 -0
  176. data/app/views/shared/icons/_search.html.erb +3 -0
  177. data/app/views/shared/icons/_search_minus.html.erb +10 -0
  178. data/app/views/shared/icons/_search_plus.html.erb +10 -0
  179. data/app/views/shared/icons/_server_error.html.erb +6 -0
  180. data/app/views/shared/icons/_share.html.erb +3 -0
  181. data/app/views/shared/icons/_shield_check.html.erb +3 -0
  182. data/app/views/shared/icons/_sign_in.html.erb +3 -0
  183. data/app/views/shared/icons/_spinner.html.erb +4 -0
  184. data/app/views/shared/icons/_star.html.erb +3 -0
  185. data/app/views/shared/icons/_store.html.erb +10 -0
  186. data/app/views/shared/icons/_sun.html.erb +3 -0
  187. data/app/views/shared/icons/_sync.html.erb +3 -0
  188. data/app/views/shared/icons/_table.html.erb +3 -0
  189. data/app/views/shared/icons/_tag.html.erb +3 -0
  190. data/app/views/shared/icons/_tags.html.erb +11 -0
  191. data/app/views/shared/icons/_tools.html.erb +4 -0
  192. data/app/views/shared/icons/_trash.html.erb +3 -0
  193. data/app/views/shared/icons/_undo.html.erb +3 -0
  194. data/app/views/shared/icons/_unlock.html.erb +3 -0
  195. data/app/views/shared/icons/_upload.html.erb +3 -0
  196. data/app/views/shared/icons/_user.html.erb +3 -0
  197. data/app/views/shared/icons/_user_circle.html.erb +10 -0
  198. data/app/views/shared/icons/_user_plus.html.erb +10 -0
  199. data/app/views/shared/icons/_video.html.erb +3 -0
  200. data/app/views/shared/icons/_wrench.html.erb +11 -0
  201. data/app/views/style_guide/index.html.erb +77 -0
  202. data/app/views/style_guide/sections/_alerts.html.erb +114 -0
  203. data/app/views/style_guide/sections/_badges.html.erb +78 -0
  204. data/app/views/style_guide/sections/_buttons.html.erb +130 -0
  205. data/app/views/style_guide/sections/_cards.html.erb +84 -0
  206. data/app/views/style_guide/sections/_colors.html.erb +106 -0
  207. data/app/views/style_guide/sections/_file_upload.html.erb +135 -0
  208. data/app/views/style_guide/sections/_forms.html.erb +129 -0
  209. data/app/views/style_guide/sections/_gradients.html.erb +253 -0
  210. data/app/views/style_guide/sections/_header.html.erb +12 -0
  211. data/app/views/style_guide/sections/_icons.html.erb +55 -0
  212. data/app/views/style_guide/sections/_images.html.erb +40 -0
  213. data/app/views/style_guide/sections/_loading.html.erb +242 -0
  214. data/app/views/style_guide/sections/_pagination.html.erb +212 -0
  215. data/app/views/style_guide/sections/_profile_components.html.erb +203 -0
  216. data/app/views/style_guide/sections/_theme_switcher.html.erb +72 -0
  217. data/app/views/style_guide/sections/_typography.html.erb +65 -0
  218. data/bin/ai-optimize-claude-md +540 -0
  219. data/bin/ai-review-local +345 -0
  220. data/bin/ai-security-review +585 -0
  221. data/bin/brakeman +9 -0
  222. data/bin/install-hooks +57 -0
  223. data/bin/rake +7 -0
  224. data/bin/rubocop +10 -0
  225. data/bin/verify_setup.rb +31 -0
  226. data/config/brakeman.ignore +28 -0
  227. data/config/initializers/neon_sakura.rb +15 -0
  228. data/config/license_overrides.yml +13 -0
  229. data/config/routes.rb +21 -0
  230. data/config/theme_mappings.yml +61 -0
  231. data/docs/PRYSM_ASSETS.md +210 -0
  232. data/docs/plans/extract_ai_reviewer_plan.md +151 -0
  233. data/docs/plans/neon_sakura_gem_plan.md +138 -0
  234. data/lib/neon_sakura/configuration.rb +94 -0
  235. data/lib/neon_sakura/engine.rb +48 -0
  236. data/lib/neon_sakura/icon_helper.rb +54 -0
  237. data/lib/neon_sakura/profile_helper.rb +24 -0
  238. data/lib/neon_sakura/stylesheet_helper.rb +40 -0
  239. data/lib/neon_sakura/theme_helper.rb +63 -0
  240. data/lib/neon_sakura/theme_importer.rb +112 -0
  241. data/lib/neon_sakura/version.rb +5 -0
  242. data/lib/neon_sakura.rb +13 -0
  243. data/neon_sakura.gemspec +50 -0
  244. data/package.json +18 -0
  245. data/scripts/git-hooks/post-merge +132 -0
  246. data/scripts/git-hooks/pre-commit +123 -0
  247. data/scripts/git-hooks/pre-push +127 -0
  248. data/scripts/license-check.rb +587 -0
  249. data/settings.local.json +12 -0
  250. data/yarn.lock +778 -0
  251. metadata +503 -0
@@ -0,0 +1,587 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # License checking script for commercial Ruby projects
5
+ # Checks gem licenses against a whitelist of commercial-friendly licenses
6
+
7
+ require 'json'
8
+ require 'bundler'
9
+ require 'net/http'
10
+ require 'uri'
11
+ require 'open3'
12
+
13
+ class LicenseChecker
14
+ # Commercial-friendly license whitelist
15
+ APPROVED_LICENSES = Set.new([
16
+ 'MIT',
17
+ 'Apache-2.0',
18
+ 'Apache 2.0',
19
+ 'Apache License 2.0',
20
+ 'Apache Software License',
21
+ 'Apache',
22
+ 'HPND',
23
+ 'BSD-2-Clause',
24
+ 'BSD-3-Clause',
25
+ 'BSD',
26
+ 'ISC',
27
+ 'CC0-1.0',
28
+ 'Unlicense',
29
+ 'Public Domain',
30
+ 'Ruby',
31
+ 'Ruby License',
32
+ 'Simplified BSD',
33
+ 'Modified BSD',
34
+ 'New BSD',
35
+ 'BSD-2-Clause-Patent',
36
+ 'BSD-3-Clause-Clear',
37
+ 'CC-BY-3.0',
38
+ 'CC-BY-4.0',
39
+ 'WTFPL',
40
+ 'Zlib',
41
+ 'PostgreSQL',
42
+ 'OpenSSL',
43
+ 'SSLeay',
44
+ 'curl',
45
+ 'libpng',
46
+ 'libjpeg',
47
+ 'FTL',
48
+ 'Bzip2',
49
+ 'zlib/libpng',
50
+ 'MIT-0',
51
+ 'MPL-2.0',
52
+ 'Mozilla Public License 2.0',
53
+ 'EPL-1.0',
54
+ 'EPL-2.0',
55
+ 'Eclipse Public License 1.0',
56
+ 'Eclipse Public License 2.0',
57
+ 'CDDL-1.0',
58
+ 'CDDL-1.1',
59
+ 'Common Development and Distribution License 1.0',
60
+ 'Common Development and Distribution License 1.1',
61
+ 'X11',
62
+ 'MIT/X11',
63
+ 'Artistic-2.0',
64
+ 'Artistic License 2.0',
65
+ 'Unicode-TOU',
66
+ 'Unicode License Agreement - Data Files and Software',
67
+ 'W3C',
68
+ 'W3C Software Notice and License',
69
+ 'JSON',
70
+ 'same as ruby',
71
+ 'same as ruby\'s',
72
+ 'Ruby\'s',
73
+ 'same as Ruby\'s',
74
+ 'Ruby License',
75
+ 'same as Ruby',
76
+ 'Ruby license',
77
+ 'same as ruby license',
78
+ 'Ruby License/GPL',
79
+ 'Ruby/GPL',
80
+ 'GPL-2.0+ OR Ruby',
81
+ 'GPL-3.0+ OR Ruby',
82
+ 'BSD-2-Clause OR Ruby',
83
+ 'BSD-3-Clause OR Ruby',
84
+ 'MIT OR Ruby',
85
+ 'Apache-2.0 OR Ruby',
86
+ 'Dual licensed under Ruby or GPLv2+',
87
+ 'Dual licensed under Ruby or GPLv3+',
88
+ 'Dual MIT/GPL',
89
+ 'MIT OR GPL-2.0',
90
+ 'MIT OR GPL-3.0',
91
+ 'Apache-2.0 OR MIT',
92
+ 'BSD-2-Clause OR MIT',
93
+ 'BSD-3-Clause OR MIT',
94
+ 'GPL-2.0+ OR MIT',
95
+ 'GPL-3.0+ OR MIT',
96
+ 'LGPL-2.1',
97
+ 'LGPL-2.1+',
98
+ 'LGPL-3.0',
99
+ 'LGPL-3.0+',
100
+ 'Lesser General Public License 2.1',
101
+ 'Lesser General Public License 3.0',
102
+ 'GNU Lesser General Public License v2.1',
103
+ 'GNU Lesser General Public License v3.0',
104
+ 'GPL-2.0+ OR LGPL-2.1+',
105
+ 'GPL-3.0+ OR LGPL-3.0+',
106
+ 'Nonstandard',
107
+ 'none',
108
+ 'unknown',
109
+ 'proprietary',
110
+ 'Brakeman Public Use License'
111
+ ]).freeze
112
+
113
+ # Known problematic licenses that require override
114
+ PROBLEMATIC_LICENSES = Set.new([
115
+ 'GPL-2.0',
116
+ 'GPL-3.0',
117
+ 'GPL-2.0+',
118
+ 'GPL-3.0+',
119
+ 'GPLv2',
120
+ 'GPLv3',
121
+ 'GNU GPL v2',
122
+ 'GNU GPL v3',
123
+ 'GNU General Public License v2.0',
124
+ 'GNU General Public License v3.0',
125
+ 'AGPL-3.0',
126
+ 'AGPL-3.0+',
127
+ 'GNU Affero General Public License v3.0',
128
+ 'CC-BY-SA-3.0',
129
+ 'CC-BY-SA-4.0',
130
+ 'Creative Commons Attribution-ShareAlike 3.0',
131
+ 'Creative Commons Attribution-ShareAlike 4.0',
132
+ 'EUPL-1.1',
133
+ 'EUPL-1.2',
134
+ 'European Union Public License 1.1',
135
+ 'European Union Public License 1.2',
136
+ 'CPAL-1.0',
137
+ 'Common Public Attribution License 1.0',
138
+ 'OSL-3.0',
139
+ 'Open Software License 3.0',
140
+ 'QPL-1.0',
141
+ 'Q Public License 1.0',
142
+ 'Sleepycat',
143
+ 'Berkeley Database License',
144
+ 'CPOL-1.02',
145
+ 'Code Project Open License 1.02'
146
+ ]).freeze
147
+
148
+ def initialize
149
+ @override_file = 'config/license_overrides.yml'
150
+ @overrides = load_overrides
151
+ @exit_code = 0
152
+ @python_requirements = find_python_requirements
153
+ @ml_models = find_ml_models
154
+ end
155
+
156
+ def check_licenses
157
+ puts '🔍 Checking licenses for commercial compatibility...'
158
+ puts '📦 Scanning Ruby gems...'
159
+
160
+ problematic_gems = []
161
+ unknown_gems = []
162
+
163
+ # Get all gems and their specifications
164
+ Bundler.load.specs.each do |spec|
165
+ next if spec.name == 'bundler' # Skip bundler itself
166
+
167
+ licenses = get_gem_licenses(spec)
168
+
169
+ # Check if gem is overridden (for both licensed and unlicensed gems)
170
+ if @overrides.dig('approved_gems', spec.name)
171
+ puts "✅ #{spec.name} (#{spec.version}) - OVERRIDE: #{@overrides['approved_gems'][spec.name]}"
172
+ next
173
+ end
174
+
175
+ if licenses.empty?
176
+ unknown_gems << { name: spec.name, version: spec.version.to_s, type: 'ruby' }
177
+ next
178
+ end
179
+
180
+ # Check licenses
181
+ approved = licenses.any? { |license| license_approved?(license) }
182
+
183
+ if approved
184
+ puts "✅ 💎 #{spec.name} (#{spec.version}) - #{licenses.join(', ')}"
185
+ else
186
+ problematic_gems << {
187
+ name: spec.name,
188
+ version: spec.version.to_s,
189
+ type: 'ruby',
190
+ licenses: licenses,
191
+ problematic: licenses.any? { |license| PROBLEMATIC_LICENSES.include?(license) }
192
+ }
193
+ end
194
+ end
195
+
196
+ # Check Python dependencies
197
+ puts "\n🐍 Scanning Python dependencies..."
198
+ python_results = check_python_dependencies
199
+
200
+ # Check ML models
201
+ puts "\n🤖 Scanning ML models..."
202
+ model_results = check_ml_models
203
+
204
+ # Check Docker containers
205
+ puts "\n🐳 Scanning Docker containers..."
206
+ container_results = check_docker_containers
207
+
208
+ # Combine all results
209
+ all_problematic = problematic_gems + python_results[:problematic] + model_results[:problematic] + container_results[:problematic]
210
+ all_unknown = unknown_gems + python_results[:unknown] + model_results[:unknown] + container_results[:unknown]
211
+
212
+ # Report results
213
+ if all_problematic.any?
214
+ puts "\n❌ PROBLEMATIC LICENSES FOUND:"
215
+ all_problematic.each do |item|
216
+ severity = item[:problematic] ? '🚨 BLOCKED' : '⚠️ REVIEW'
217
+ type_icon = get_type_icon(item[:type])
218
+ puts " #{severity} #{type_icon} #{item[:name]} (#{item[:version]}) - #{item[:licenses].join(', ')}"
219
+ end
220
+
221
+ puts "\n💡 To approve items, add them to #{@override_file}:"
222
+ puts 'approved_gems:'
223
+ all_problematic.each do |item|
224
+ puts " #{item[:name]}: \"Commercial license approved - [reason]\""
225
+ end
226
+
227
+ @exit_code = 1
228
+ end
229
+
230
+ if all_unknown.any?
231
+ puts "\n⚠️ UNKNOWN LICENSES (please investigate):"
232
+ all_unknown.each do |item|
233
+ type_icon = get_type_icon(item[:type])
234
+ puts " #{type_icon} #{item[:name]} (#{item[:version]}) - No license information"
235
+ end
236
+
237
+ puts "\n💡 To approve items with unknown licenses, add them to #{@override_file}:"
238
+ puts 'approved_gems:'
239
+ all_unknown.each do |item|
240
+ puts " #{item[:name]}: \"License verified manually - [details]\""
241
+ end
242
+
243
+ @exit_code = 1
244
+ end
245
+
246
+ if @exit_code.zero?
247
+ puts "\n✅ All licenses are commercial-friendly!"
248
+ puts '📊 Summary:'
249
+ puts " 💎 Ruby gems: #{Bundler.load.specs.count - 1} checked"
250
+ puts " 🐍 Python packages: #{python_results[:total]} checked"
251
+ puts " 🤖 ML models: #{model_results[:total]} checked"
252
+ puts " 🐳 Docker containers: #{container_results[:total]} checked"
253
+ else
254
+ puts "\n❌ License check failed. Please review the items above."
255
+ end
256
+
257
+ exit @exit_code
258
+ end
259
+
260
+ private
261
+
262
+ def find_python_requirements
263
+ # Use the unified requirements.txt at project root
264
+ # requirements_files << 'requirements.txt' if File.exist?('requirements.txt')
265
+ # requirements_files
266
+
267
+ [ 'docker/donut-service/requirements.txt' ]
268
+ end
269
+
270
+ def find_ml_models
271
+ models = []
272
+ # Search for model references in Python files
273
+ Dir.glob('**/*.py').each do |file|
274
+ content = File.read(file)
275
+ # Look for Hugging Face model references
276
+ content.scan(%r{["']([\w-]+/[\w-]+)["']}) do |match|
277
+ model_name = match[0]
278
+ models << { name: model_name, file: file } if model_name.include?('/') && !model_name.include?('http')
279
+ end
280
+ end
281
+ models.uniq
282
+ end
283
+
284
+ def check_python_dependencies
285
+ problematic = []
286
+ unknown = []
287
+ total = 0
288
+
289
+ @python_requirements.each do |req_file|
290
+ next unless File.exist?(req_file)
291
+
292
+ File.readlines(req_file).each do |line|
293
+ line.strip!
294
+ next if line.empty? || line.start_with?('#')
295
+
296
+ # Parse package name and version
297
+ package_name = line.split(/[=<>!]/)[0].strip
298
+ # Handle uvicorn[standard] style package names
299
+ package_name = package_name.split('[')[0] if package_name.include?('[')
300
+ total += 1
301
+
302
+ # Check if overridden
303
+ if @overrides.dig('approved_gems', package_name)
304
+ puts "✅ 🐍 #{package_name} - OVERRIDE: #{@overrides['approved_gems'][package_name]}"
305
+ next
306
+ end
307
+
308
+ # Get license from PyPI
309
+ license_info = get_pypi_license(package_name)
310
+
311
+ if license_info.nil?
312
+ unknown << {
313
+ name: package_name,
314
+ version: line.split('==')[1] || 'latest',
315
+ type: 'python',
316
+ licenses: []
317
+ }
318
+ elsif license_approved?(license_info)
319
+ puts "✅ 🐍 #{package_name} - #{license_info}"
320
+ else
321
+ problematic << {
322
+ name: package_name,
323
+ version: line.split('==')[1] || 'latest',
324
+ type: 'python',
325
+ licenses: [ license_info ],
326
+ problematic: PROBLEMATIC_LICENSES.include?(license_info)
327
+ }
328
+ end
329
+ end
330
+ end
331
+
332
+ { problematic: problematic, unknown: unknown, total: total }
333
+ end
334
+
335
+ def check_ml_models
336
+ problematic = []
337
+ unknown = []
338
+ total = 0
339
+
340
+ @ml_models.each do |model_info|
341
+ total += 1
342
+ model_name = model_info[:name]
343
+
344
+ # Check if overridden
345
+ if @overrides.dig('approved_gems', model_name)
346
+ puts "✅ 🤖 #{model_name} - OVERRIDE: #{@overrides['approved_gems'][model_name]}"
347
+ next
348
+ end
349
+
350
+ # Get license from Hugging Face API
351
+ license_info = get_huggingface_license(model_name)
352
+
353
+ if license_info.nil?
354
+ unknown << {
355
+ name: model_name,
356
+ version: 'latest',
357
+ type: 'ml_model',
358
+ licenses: []
359
+ }
360
+ elsif license_approved?(license_info)
361
+ puts "✅ 🤖 #{model_name} - #{license_info}"
362
+ else
363
+ problematic << {
364
+ name: model_name,
365
+ version: 'latest',
366
+ type: 'ml_model',
367
+ licenses: [ license_info ],
368
+ problematic: PROBLEMATIC_LICENSES.include?(license_info)
369
+ }
370
+ end
371
+ end
372
+
373
+ { problematic: problematic, unknown: unknown, total: total }
374
+ end
375
+
376
+ def check_docker_containers
377
+ problematic = []
378
+ unknown = []
379
+ total = 0
380
+
381
+ # Check Dockerfiles for base images and their licenses
382
+ dockerfiles = Dir.glob('**/Dockerfile*')
383
+
384
+ dockerfiles.each do |dockerfile|
385
+ content = File.read(dockerfile)
386
+
387
+ # Extract FROM statements
388
+ content.scan(/^FROM\s+([^\s]+)/) do |match|
389
+ image_name = match[0]
390
+ next if image_name == 'scratch' || image_name.start_with?('$') || image_name == 'base'
391
+
392
+ total += 1
393
+
394
+ # Check if overridden
395
+ if @overrides.dig('approved_gems', image_name)
396
+ puts "✅ 🐳 #{image_name} - OVERRIDE: #{@overrides['approved_gems'][image_name]}"
397
+ next
398
+ end
399
+
400
+ # For common base images, assume they're fine
401
+ if image_name.match?(/(ubuntu|debian|alpine|python|node|ruby):/)
402
+ puts "✅ 🐳 #{image_name} - Standard base image (assumed MIT/Apache compatible)"
403
+ else
404
+ unknown << {
405
+ name: image_name,
406
+ version: 'latest',
407
+ type: 'docker',
408
+ licenses: []
409
+ }
410
+ end
411
+ end
412
+ end
413
+
414
+ { problematic: problematic, unknown: unknown, total: total }
415
+ end
416
+
417
+ def get_pypi_license(package_name)
418
+ uri = URI("https://pypi.org/pypi/#{package_name}/json")
419
+ response = Net::HTTP.get_response(uri)
420
+
421
+ return nil unless response.code == '200'
422
+
423
+ data = JSON.parse(response.body)
424
+ license_info = data.dig('info', 'license')
425
+
426
+ # Try classifiers if license field is empty
427
+ if license_info.nil? || license_info.empty?
428
+ classifiers = data.dig('info', 'classifiers') || []
429
+ license_classifiers = classifiers.select { |c| c.start_with?('License ::') }
430
+
431
+ unless license_classifiers.empty?
432
+ # Extract license from classifier like "License :: OSI Approved :: MIT License"
433
+ license_info = license_classifiers.first.split(' :: ').last
434
+ end
435
+ end
436
+
437
+ normalize_license(license_info)
438
+ rescue StandardError => e
439
+ puts "⚠️ Error fetching PyPI info for #{package_name}: #{e.message}"
440
+ nil
441
+ end
442
+
443
+ def get_huggingface_license(model_name)
444
+ uri = URI("https://huggingface.co/api/models/#{model_name}")
445
+ response = Net::HTTP.get_response(uri)
446
+
447
+ return nil unless response.code == '200'
448
+
449
+ data = JSON.parse(response.body)
450
+ license_info = data['license']
451
+
452
+ normalize_license(license_info)
453
+ rescue StandardError => e
454
+ puts "⚠️ Error fetching Hugging Face info for #{model_name}: #{e.message}"
455
+ nil
456
+ end
457
+
458
+ def get_type_icon(type)
459
+ case type
460
+ when 'ruby' then '💎'
461
+ when 'python' then '🐍'
462
+ when 'ml_model' then '🤖'
463
+ when 'docker' then '🐳'
464
+ else '📦'
465
+ end
466
+ end
467
+
468
+ def get_gem_licenses(spec)
469
+ licenses = []
470
+
471
+ # Try spec.licenses first (array)
472
+ if spec.respond_to?(:licenses) && spec.licenses.any?
473
+ licenses = spec.licenses
474
+ elsif spec.respond_to?(:license) && spec.license
475
+ licenses = [ spec.license ]
476
+ end
477
+
478
+ # Normalize license names
479
+ licenses.map { |license| normalize_license(license) }.compact.uniq
480
+ end
481
+
482
+ def normalize_license(license)
483
+ return nil if license.nil? || license.strip.empty?
484
+
485
+ normalized = license.strip
486
+
487
+ # If license text is very long (likely full license text), try to extract just the license name
488
+ if normalized.length > 200
489
+ # Look for common license identifiers in long text
490
+ case normalized.downcase
491
+ when /bsd.*3.*clause/i
492
+ return 'BSD-3-Clause'
493
+ when /bsd.*2.*clause/i
494
+ return 'BSD-2-Clause'
495
+ when /bsd/i
496
+ return 'BSD'
497
+ when /mit/i
498
+ return 'MIT'
499
+ when /apache.*2/i
500
+ return 'Apache-2.0'
501
+ when /apache/i
502
+ return 'Apache'
503
+ when /gpl.*3/i
504
+ return 'GPL-3.0'
505
+ when /gpl.*2/i
506
+ return 'GPL-2.0'
507
+ when /lgpl.*3/i
508
+ return 'LGPL-3.0'
509
+ when /lgpl.*2/i
510
+ return 'LGPL-2.1'
511
+ when /mozilla.*public.*license/i
512
+ return 'MPL-2.0'
513
+ else
514
+ # Truncate very long license text and add indicator
515
+ return "#{normalized[0..100]}... [truncated - full license text]"
516
+ end
517
+ end
518
+
519
+ # Common normalizations for shorter license names
520
+ case normalized.downcase
521
+ when 'mit license', 'mit-license', 'mit (http://opensource.org/licenses/mit-license.php)'
522
+ 'MIT'
523
+ when 'apache license, version 2.0', 'apache license 2.0', 'apache-2.0 license', 'apache 2.0 license'
524
+ 'Apache-2.0'
525
+ when 'bsd license', 'bsd-style', 'bsd style'
526
+ 'BSD'
527
+ when 'bsd 3-clause license', 'bsd-3-clause'
528
+ 'BSD-3-Clause'
529
+ when 'bsd 2-clause license', 'bsd-2-clause'
530
+ 'BSD-2-Clause'
531
+ when 'gpl-2.0', 'gpl v2', 'gpl version 2', 'gnu gpl v2'
532
+ 'GPL-2.0'
533
+ when 'gpl-3.0', 'gpl v3', 'gpl version 3', 'gnu gpl v3'
534
+ 'GPL-3.0'
535
+ when 'lgpl-2.1', 'lgpl v2.1', 'lgpl version 2.1', 'gnu lgpl v2.1'
536
+ 'LGPL-2.1'
537
+ when 'lgpl-3.0', 'lgpl v3', 'lgpl version 3', 'gnu lgpl v3'
538
+ 'LGPL-3.0'
539
+ when 'mozilla public license 2.0'
540
+ 'MPL-2.0'
541
+ when 'eclipse public license 1.0'
542
+ 'EPL-1.0'
543
+ when 'eclipse public license 2.0'
544
+ 'EPL-2.0'
545
+ when 'artistic license 2.0'
546
+ 'Artistic-2.0'
547
+ when 'public domain'
548
+ 'Public Domain'
549
+ when 'unlicense'
550
+ 'Unlicense'
551
+ when 'cc0', 'cc0 1.0', 'cc0-1.0 universal'
552
+ 'CC0-1.0'
553
+ when 'ruby license', 'ruby\'s license', 'same as ruby\'s license'
554
+ 'Ruby'
555
+ when 'isc license'
556
+ 'ISC'
557
+ when 'zlib license'
558
+ 'Zlib'
559
+ when 'postgresql license'
560
+ 'PostgreSQL'
561
+ else
562
+ # Still truncate if somehow a long license made it here
563
+ normalized.length > 100 ? "#{normalized[0..100]}..." : normalized
564
+ end
565
+ end
566
+
567
+ def license_approved?(license)
568
+ APPROVED_LICENSES.include?(license) ||
569
+ APPROVED_LICENSES.any? { |approved| license.match?(/\b#{Regexp.escape(approved)}\b/i) }
570
+ end
571
+
572
+ def load_overrides
573
+ return {} unless File.exist?(@override_file)
574
+
575
+ require 'yaml'
576
+ YAML.load_file(@override_file) || {}
577
+ rescue StandardError => e
578
+ puts "⚠️ Warning: Could not load license overrides: #{e.message}"
579
+ {}
580
+ end
581
+ end
582
+
583
+ # Run license check
584
+ if __FILE__ == $PROGRAM_NAME
585
+ checker = LicenseChecker.new
586
+ checker.check_licenses
587
+ end
@@ -0,0 +1,12 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "WebSearch",
5
+ "Bash(chmod:*)",
6
+ "Bash(gem install:*)",
7
+ "Bash(ruby:*)"
8
+ ],
9
+ "deny": [],
10
+ "ask": []
11
+ }
12
+ }