rosett-ai 1.3.3

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 (527) hide show
  1. checksums.yaml +7 -0
  2. data/.ai-provenance.yml +119 -0
  3. data/.debride_whitelist +186 -0
  4. data/.fasterer.yml +29 -0
  5. data/.mdl_style.rb +10 -0
  6. data/.mdlrc +3 -0
  7. data/.mutant.yml +49 -0
  8. data/.namespace-allowlist +42 -0
  9. data/.reek.yml +1040 -0
  10. data/.rosett-ai/config.yml +3 -0
  11. data/.rspec +5 -0
  12. data/.rubocop.yml +380 -0
  13. data/.ruby-version +1 -0
  14. data/.yamllint +51 -0
  15. data/.yardopts +12 -0
  16. data/AI-DISCLOSURE.md +48 -0
  17. data/CHANGELOG.md +519 -0
  18. data/CLAUDE.md +141 -0
  19. data/CONTRIBUTING.md +734 -0
  20. data/INSTALL.md +154 -0
  21. data/LICENSE +674 -0
  22. data/LICENSE.md +675 -0
  23. data/QUICKSTART.md +73 -0
  24. data/README.md +366 -0
  25. data/Rakefile +200 -0
  26. data/SECURITY.md +114 -0
  27. data/bin/rai +1 -0
  28. data/cliff.toml +52 -0
  29. data/conf/adopt_redactions.yml +8 -0
  30. data/conf/behaviour/.gitkeep +0 -0
  31. data/conf/compliance/cra_rules.yml +25 -0
  32. data/conf/compliance/license_rules.yml +20 -0
  33. data/conf/design/aaif_alignment.yml +181 -0
  34. data/conf/design/ab_testing.yml +172 -0
  35. data/conf/design/accessibility.yml +84 -0
  36. data/conf/design/ai_authorship.yml +210 -0
  37. data/conf/design/ai_provenance.yml +224 -0
  38. data/conf/design/ai_tool_configuration.yml +207 -0
  39. data/conf/design/architecture.yml +139 -0
  40. data/conf/design/autocompletion.yml +115 -0
  41. data/conf/design/backward_compatibility.yml +112 -0
  42. data/conf/design/behaviour_composition.yml +246 -0
  43. data/conf/design/build_rake_extraction.yml +57 -0
  44. data/conf/design/ci_pipeline.yml +100 -0
  45. data/conf/design/claude_code_configuration.yml +157 -0
  46. data/conf/design/compiler.yml +128 -0
  47. data/conf/design/comply.yml +153 -0
  48. data/conf/design/content_packs.yml +84 -0
  49. data/conf/design/desktop_integration.yml +289 -0
  50. data/conf/design/distribution.yml +216 -0
  51. data/conf/design/doctor.yml +184 -0
  52. data/conf/design/documentation.yml +152 -0
  53. data/conf/design/engine_architecture.yml +257 -0
  54. data/conf/design/error_handling.yml +103 -0
  55. data/conf/design/feature_flags.yml +142 -0
  56. data/conf/design/git_hooks.yml +165 -0
  57. data/conf/design/gui_plugins.yml +475 -0
  58. data/conf/design/i18n.yml +84 -0
  59. data/conf/design/integration_testing.yml +56 -0
  60. data/conf/design/licensing_system.yml +88 -0
  61. data/conf/design/lifecycle_management.yml +208 -0
  62. data/conf/design/mcp_integration.yml +207 -0
  63. data/conf/design/mcp_settings.yml +126 -0
  64. data/conf/design/migration.yml +56 -0
  65. data/conf/design/monitoring_observability.yml +194 -0
  66. data/conf/design/namespace_cleanup.yml +145 -0
  67. data/conf/design/plugin_test_segregation.yml +145 -0
  68. data/conf/design/policy_management.yml +229 -0
  69. data/conf/design/project_management.yml +183 -0
  70. data/conf/design/rai_mcp_asset_discovery.yml +164 -0
  71. data/conf/design/rai_mcp_server.yml +605 -0
  72. data/conf/design/release_management.yml +117 -0
  73. data/conf/design/retrofit.yml +199 -0
  74. data/conf/design/retrospective_analyzer.yml +79 -0
  75. data/conf/design/scope_hierarchy.yml +352 -0
  76. data/conf/design/security.yml +115 -0
  77. data/conf/design/session_retrospective.yml +85 -0
  78. data/conf/design/smart_ui_feedback.yml +89 -0
  79. data/conf/design/structured_logging.yml +148 -0
  80. data/conf/design/styles.yml +123 -0
  81. data/conf/design/test_peer_review.yml +89 -0
  82. data/conf/design/testing.yml +136 -0
  83. data/conf/design/threat_model.yml +108 -0
  84. data/conf/design/ui_framework.yml +111 -0
  85. data/conf/design/usage_optimization.yml +122 -0
  86. data/conf/design/version_management.yml +60 -0
  87. data/conf/design/workflow.yml +227 -0
  88. data/conf/mcp/server_defaults.yml +42 -0
  89. data/conf/mcp/trust.yml +21 -0
  90. data/conf/packaging/core.yml +12 -0
  91. data/conf/packaging/gtk4.yml +11 -0
  92. data/conf/packaging/qt6.yml +11 -0
  93. data/conf/policy/default_deny_list.yml +197 -0
  94. data/conf/review/cli-command-audit.yml +857 -0
  95. data/conf/review/design-docs.yml +1064 -0
  96. data/conf/review/design-questionnaire.yml +153 -0
  97. data/conf/review/questionnaire.yml +146 -0
  98. data/conf/review/rosett-ai-core.yml +2919 -0
  99. data/conf/schemas/ai_config_schema.json +73 -0
  100. data/conf/schemas/behaviour_schema.json +132 -0
  101. data/conf/schemas/compliance_rule_schema.json +63 -0
  102. data/conf/schemas/content_pack_manifest_schema.json +51 -0
  103. data/conf/schemas/design_schema.json +210 -0
  104. data/conf/schemas/engine_manifest_schema.json +144 -0
  105. data/conf/schemas/lockfile_schema.json +74 -0
  106. data/conf/schemas/mcp_server_schema.json +48 -0
  107. data/conf/schemas/packaging_schema.json +70 -0
  108. data/conf/schemas/policy_schema.json +85 -0
  109. data/conf/schemas/provenance_schema.json +84 -0
  110. data/conf/schemas/rai_config_schema.json +56 -0
  111. data/conf/schemas/rai_project_schema.json +20 -0
  112. data/conf/schemas/scope_hierarchy_schema.json +49 -0
  113. data/conf/schemas/target_schema.json +67 -0
  114. data/conf/schemas/tooling_schema.json +65 -0
  115. data/conf/schemas/workflow_schema.json +112 -0
  116. data/conf/targets/agents_md.yml +17 -0
  117. data/conf/targets/claude.yml +12 -0
  118. data/conf/tooling/tools.yml +58 -0
  119. data/dist/rosett-ai-mcp.service +48 -0
  120. data/dist/rosett-ai-mcp.yml.default +45 -0
  121. data/doc/AAIF_POSITIONING.md +58 -0
  122. data/doc/ADOPT.md +224 -0
  123. data/doc/AI_PROVENANCE.md +139 -0
  124. data/doc/ARCHITECTURE.md +920 -0
  125. data/doc/BEHAVIOUR.md +409 -0
  126. data/doc/BUILD.md +138 -0
  127. data/doc/CI_CD_RECIPES.md +171 -0
  128. data/doc/CLAUDE_SESSIONS_MOVED.md +16 -0
  129. data/doc/COMMAND_ANALYSIS.md +229 -0
  130. data/doc/CONFIGURATION.md +281 -0
  131. data/doc/DESIGN_AUDIT.md +235 -0
  132. data/doc/DESIGN_PEER_REVIEW.md +771 -0
  133. data/doc/DESKTOP.md +447 -0
  134. data/doc/ENGINES.md +567 -0
  135. data/doc/ENGINE_DEVELOPMENT_GUIDE.md +417 -0
  136. data/doc/FEATURE_AUDIT.md +218 -0
  137. data/doc/IMPLEMENTATION_PLAN.md +669 -0
  138. data/doc/INCIDENT_REPORT_2026-02-02.md +251 -0
  139. data/doc/MIGRATION_GUIDE.md +88 -0
  140. data/doc/PACKAGING.md +232 -0
  141. data/doc/PROJECT_DASHBOARD.md +153 -0
  142. data/doc/PULP_DEPLOYMENT.md +164 -0
  143. data/doc/QUALITY_FIX_SUMMARY.md +110 -0
  144. data/doc/QUICK_START.md +162 -0
  145. data/doc/REEK_CONFIGURATION.md +166 -0
  146. data/doc/REFERENCE.md +253 -0
  147. data/doc/REFERENCES.md +324 -0
  148. data/doc/SECURITY_REVIEW_CHECKLIST.md +72 -0
  149. data/doc/SESSION_2026-02-28_GTK4_HARDENING.md +359 -0
  150. data/doc/SETUP.md +202 -0
  151. data/doc/TEST_PEER_REVIEW.md +152 -0
  152. data/doc/THREAT_MODEL.md +230 -0
  153. data/doc/USAGE.md +545 -0
  154. data/doc/USER_MANUAL.md +585 -0
  155. data/doc/ai_test_review_checklist.md +110 -0
  156. data/doc/changes/2026-02-18-packaging-fpm.md +155 -0
  157. data/doc/changes/2026-02-19-testing-infrastructure.md +221 -0
  158. data/doc/changes/2026-02-20-security-implementation.md +281 -0
  159. data/doc/changes/2026-02-20-styles-implementation.md +220 -0
  160. data/doc/changes/2026-02-21-architecture-completion.md +95 -0
  161. data/doc/changes/2026-02-21-architecture-ui-layer.md +253 -0
  162. data/doc/changes/2026-02-21-cc-config-implementation.md +108 -0
  163. data/doc/changes/2026-02-21-ci-pipeline-implementation.md +214 -0
  164. data/doc/changes/2026-02-21-compiler-multi-target-pipeline.md +241 -0
  165. data/doc/changes/2026-02-21-config-design-show-commands.md +61 -0
  166. data/doc/changes/2026-02-21-design-implementation-overview.md +455 -0
  167. data/doc/changes/2026-02-21-lifecycle-management.md +196 -0
  168. data/doc/changes/2026-02-21-path-resolver.md +128 -0
  169. data/doc/changes/2026-02-24-ci-tmpdir-mutant-fetch.md +45 -0
  170. data/doc/changes/2026-03-01-ci-bundler-strategy.md +120 -0
  171. data/doc/changes/2026-03-20-security-hardening-phase2.md +163 -0
  172. data/doc/context/SESSION-HANDOFF.md +69 -0
  173. data/doc/context/ai-engine-usage-trends-2026.md +80 -0
  174. data/doc/context/plan-pluggable-engines.md +590 -0
  175. data/doc/decisions/001-flog-deferred.md +32 -0
  176. data/doc/decisions/002-path-resolution-strategy.md +158 -0
  177. data/doc/decisions/003-ui-adapter-selection.md +193 -0
  178. data/doc/decisions/004-design-document-validation.md +179 -0
  179. data/doc/decisions/005-package-splitting-strategy.md +200 -0
  180. data/doc/decisions/006-multi-engine-architecture.md +147 -0
  181. data/doc/decisions/007-engine-agnostic-pivot.md +219 -0
  182. data/doc/decisions/008-ci-bundler-strategy.md +129 -0
  183. data/doc/decisions/009-core-only-v1-release.md +60 -0
  184. data/doc/decisions/010-engine-debian-packaging.md +66 -0
  185. data/doc/decisions/011-context-aware-cli.md +71 -0
  186. data/doc/dependency_decisions.yml +247 -0
  187. data/doc/issues/001-wrapper-missing-environment-variables.md +197 -0
  188. data/doc/issues/002-embedded-ruby-wrong-prefix.md +217 -0
  189. data/doc/issues/003-smoke-test-false-positive.md +127 -0
  190. data/doc/issues/004-market-research-design-updates.md +109 -0
  191. data/doc/issues/005-compile-scope-coexistence.md +161 -0
  192. data/doc/locales/.gitkeep +0 -0
  193. data/doc/man/rai.1.ronn +505 -0
  194. data/doc/operations/packaging.md +133 -0
  195. data/doc/operations/rosett-ai-release.md +65 -0
  196. data/doc/reference/error-catalog.md +107 -0
  197. data/doc/reference/rosett-ai-technical-reference.pdf +0 -0
  198. data/doc/reference/src/Pictures/cover.jpg +0 -0
  199. data/doc/reference/src/Pictures/head1.jpg +0 -0
  200. data/doc/reference/src/Pictures/head2.jpg +0 -0
  201. data/doc/reference/src/Pictures/head3.jpg +0 -0
  202. data/doc/reference/src/Pictures/head4.jpg +0 -0
  203. data/doc/reference/src/Pictures/head5.jpg +0 -0
  204. data/doc/reference/src/Pictures/head6.jpg +0 -0
  205. data/doc/reference/src/Pictures/head7.jpg +0 -0
  206. data/doc/reference/src/Pictures/head8.jpg +0 -0
  207. data/doc/reference/src/StyleInd.ist +4 -0
  208. data/doc/reference/src/bibliography.bib +79 -0
  209. data/doc/reference/src/main.tex +1288 -0
  210. data/doc/reference/src/structure.tex +303 -0
  211. data/doc/rosett-ai-bookmarks.html +301 -0
  212. data/kitchen.yml +46 -0
  213. data/lib/rosett_ai/adopter/executor_resolver.rb +77 -0
  214. data/lib/rosett_ai/adopter/local_analysis_collector.rb +154 -0
  215. data/lib/rosett_ai/adopter/rule_adopter.rb +254 -0
  216. data/lib/rosett_ai/ai_config/config_compiler.rb +111 -0
  217. data/lib/rosett_ai/ai_config/context_window.rb +55 -0
  218. data/lib/rosett_ai/ai_config/cost_controls.rb +44 -0
  219. data/lib/rosett_ai/ai_config/fallback_chain.rb +64 -0
  220. data/lib/rosett_ai/ai_config/model_router.rb +121 -0
  221. data/lib/rosett_ai/ai_config/validator.rb +45 -0
  222. data/lib/rosett_ai/authorship/attribution_compiler.rb +99 -0
  223. data/lib/rosett_ai/authorship/disclosure_policy.rb +81 -0
  224. data/lib/rosett_ai/authorship/review_validator.rb +39 -0
  225. data/lib/rosett_ai/authorship/trailer_generator.rb +88 -0
  226. data/lib/rosett_ai/backup/compressor.rb +180 -0
  227. data/lib/rosett_ai/backup/destination.rb +91 -0
  228. data/lib/rosett_ai/behaviour/manager.rb +156 -0
  229. data/lib/rosett_ai/compiler/backend.rb +86 -0
  230. data/lib/rosett_ai/compiler/backends/agents_md_backend.rb +80 -0
  231. data/lib/rosett_ai/compiler/backends/claude_backend.rb +88 -0
  232. data/lib/rosett_ai/compiler/backends/generic_backend.rb +15 -0
  233. data/lib/rosett_ai/compiler/behaviour_compiler.rb +40 -0
  234. data/lib/rosett_ai/compiler/capability_checker.rb +104 -0
  235. data/lib/rosett_ai/compiler/compilation_pipeline.rb +361 -0
  236. data/lib/rosett_ai/compiler/compiled_output.rb +39 -0
  237. data/lib/rosett_ai/compiler/locale_compiler.rb +250 -0
  238. data/lib/rosett_ai/compiler/target_profile.rb +112 -0
  239. data/lib/rosett_ai/completion/generator.rb +101 -0
  240. data/lib/rosett_ai/completion/shells/bash_generator.rb +126 -0
  241. data/lib/rosett_ai/completion/shells/fish_generator.rb +78 -0
  242. data/lib/rosett_ai/completion/shells/zsh_generator.rb +126 -0
  243. data/lib/rosett_ai/comply/checkers/cra_checker.rb +102 -0
  244. data/lib/rosett_ai/comply/checkers/license_checker.rb +85 -0
  245. data/lib/rosett_ai/comply/checkers/spdx_header_checker.rb +98 -0
  246. data/lib/rosett_ai/comply/reporter.rb +113 -0
  247. data/lib/rosett_ai/comply/runner.rb +50 -0
  248. data/lib/rosett_ai/composition/circular_dependency_detector.rb +56 -0
  249. data/lib/rosett_ai/composition/composer.rb +158 -0
  250. data/lib/rosett_ai/composition/composition_result.rb +64 -0
  251. data/lib/rosett_ai/composition/conflict_detector.rb +53 -0
  252. data/lib/rosett_ai/composition/lockfile.rb +103 -0
  253. data/lib/rosett_ai/composition/merge_strategy.rb +131 -0
  254. data/lib/rosett_ai/composition/priority_sorter.rb +29 -0
  255. data/lib/rosett_ai/composition/scope_resolver.rb +55 -0
  256. data/lib/rosett_ai/config/compile_result.rb +37 -0
  257. data/lib/rosett_ai/config/compiler.rb +13 -0
  258. data/lib/rosett_ai/config/domain_transformer.rb +13 -0
  259. data/lib/rosett_ai/config/key_map.rb +13 -0
  260. data/lib/rosett_ai/config/masking_secret_resolver.rb +40 -0
  261. data/lib/rosett_ai/config/scope_router.rb +13 -0
  262. data/lib/rosett_ai/config/secret_resolver.rb +125 -0
  263. data/lib/rosett_ai/configuration.rb +119 -0
  264. data/lib/rosett_ai/content/content_client.rb +60 -0
  265. data/lib/rosett_ai/content/pack_installer.rb +117 -0
  266. data/lib/rosett_ai/content/pack_manifest.rb +50 -0
  267. data/lib/rosett_ai/content/pack_registry.rb +68 -0
  268. data/lib/rosett_ai/content_packs/manager.rb +50 -0
  269. data/lib/rosett_ai/dbus/compositor_detector.rb +77 -0
  270. data/lib/rosett_ai/dbus/focus_adapters/base.rb +59 -0
  271. data/lib/rosett_ai/dbus/focus_adapters/gnome_adapter.rb +172 -0
  272. data/lib/rosett_ai/dbus/focus_adapters/hyprland_adapter.rb +77 -0
  273. data/lib/rosett_ai/dbus/focus_adapters/i3_adapter.rb +65 -0
  274. data/lib/rosett_ai/dbus/focus_adapters/kwin_adapter.rb +103 -0
  275. data/lib/rosett_ai/dbus/focus_adapters/x11_adapter.rb +105 -0
  276. data/lib/rosett_ai/dbus/focus_monitor_interface.rb +103 -0
  277. data/lib/rosett_ai/dbus/manager_interface.rb +213 -0
  278. data/lib/rosett_ai/dbus/plugin_manager_interface.rb +169 -0
  279. data/lib/rosett_ai/dbus/rate_limiter.rb +89 -0
  280. data/lib/rosett_ai/dbus/service.rb +121 -0
  281. data/lib/rosett_ai/dbus/status_notifier_interface.rb +79 -0
  282. data/lib/rosett_ai/deprecation.rb +79 -0
  283. data/lib/rosett_ai/desktop/dbus_client.rb +259 -0
  284. data/lib/rosett_ai/desktop/gtk4_app.rb +371 -0
  285. data/lib/rosett_ai/desktop/gtk4_preferences.rb +331 -0
  286. data/lib/rosett_ai/desktop/gui_logger.rb +236 -0
  287. data/lib/rosett_ai/doctor/check.rb +92 -0
  288. data/lib/rosett_ai/doctor/checks/cache_health_check.rb +50 -0
  289. data/lib/rosett_ai/doctor/checks/dbus_availability_check.rb +39 -0
  290. data/lib/rosett_ai/doctor/checks/engine_detection_check.rb +46 -0
  291. data/lib/rosett_ai/doctor/checks/file_permission_check.rb +44 -0
  292. data/lib/rosett_ai/doctor/checks/gem_dependency_check.rb +55 -0
  293. data/lib/rosett_ai/doctor/checks/ruby_version_check.rb +50 -0
  294. data/lib/rosett_ai/doctor/checks/stale_config_nncc_check.rb +57 -0
  295. data/lib/rosett_ai/doctor/checks/stale_home_nncc_check.rb +59 -0
  296. data/lib/rosett_ai/doctor.rb +81 -0
  297. data/lib/rosett_ai/documentation/reference_compiler.rb +122 -0
  298. data/lib/rosett_ai/documentation/translator.rb +62 -0
  299. data/lib/rosett_ai/engines/base_config_compiler.rb +203 -0
  300. data/lib/rosett_ai/engines/detector.rb +63 -0
  301. data/lib/rosett_ai/engines/registry.rb +50 -0
  302. data/lib/rosett_ai/error_handler.rb +139 -0
  303. data/lib/rosett_ai/exit_codes.rb +76 -0
  304. data/lib/rosett_ai/feature_flags.rb +102 -0
  305. data/lib/rosett_ai/formatting.rb +33 -0
  306. data/lib/rosett_ai/gem_consistency_checker.rb +199 -0
  307. data/lib/rosett_ai/git_hooks/chain_detector.rb +86 -0
  308. data/lib/rosett_ai/git_hooks/installer.rb +175 -0
  309. data/lib/rosett_ai/git_hooks/script_generator.rb +125 -0
  310. data/lib/rosett_ai/gitlab/validators/supplementary_gitlab_ci_yaml_validator.rb +79 -0
  311. data/lib/rosett_ai/i18n/locale_resolver.rb +46 -0
  312. data/lib/rosett_ai/i18n/utf8_checker.rb +32 -0
  313. data/lib/rosett_ai/init/config_file_writer.rb +24 -0
  314. data/lib/rosett_ai/init/directory_builder.rb +38 -0
  315. data/lib/rosett_ai/init/file_copier.rb +95 -0
  316. data/lib/rosett_ai/init/global_initializer.rb +28 -0
  317. data/lib/rosett_ai/init/local_initializer.rb +27 -0
  318. data/lib/rosett_ai/init/mcp_registrar.rb +109 -0
  319. data/lib/rosett_ai/init/project_initializer.rb +38 -0
  320. data/lib/rosett_ai/licensing/license_key.rb +139 -0
  321. data/lib/rosett_ai/licensing/license_store.rb +64 -0
  322. data/lib/rosett_ai/licensing/license_validator.rb +60 -0
  323. data/lib/rosett_ai/licensing/tier.rb +42 -0
  324. data/lib/rosett_ai/mcp/admin/auditor.rb +88 -0
  325. data/lib/rosett_ai/mcp/admin/health_checker.rb +81 -0
  326. data/lib/rosett_ai/mcp/admin/registry.rb +100 -0
  327. data/lib/rosett_ai/mcp/admin/schema_validator.rb +63 -0
  328. data/lib/rosett_ai/mcp/enforcement/.gitkeep +0 -0
  329. data/lib/rosett_ai/mcp/enforcement/hook_generator.rb +197 -0
  330. data/lib/rosett_ai/mcp/enforcement/validator.rb +215 -0
  331. data/lib/rosett_ai/mcp/governance.rb +160 -0
  332. data/lib/rosett_ai/mcp/http_security_config.rb +158 -0
  333. data/lib/rosett_ai/mcp/instructions.rb +266 -0
  334. data/lib/rosett_ai/mcp/key_hasher.rb +66 -0
  335. data/lib/rosett_ai/mcp/keyfile.rb +221 -0
  336. data/lib/rosett_ai/mcp/middleware/authentication.rb +146 -0
  337. data/lib/rosett_ai/mcp/middleware/content_type.rb +56 -0
  338. data/lib/rosett_ai/mcp/middleware/cors.rb +83 -0
  339. data/lib/rosett_ai/mcp/middleware/origin_validation.rb +73 -0
  340. data/lib/rosett_ai/mcp/middleware/rate_limit.rb +106 -0
  341. data/lib/rosett_ai/mcp/middleware/request_size.rb +51 -0
  342. data/lib/rosett_ai/mcp/plugins.rb +143 -0
  343. data/lib/rosett_ai/mcp/prompts/compilation_prompt.rb +40 -0
  344. data/lib/rosett_ai/mcp/prompts/compliance_prompt.rb +41 -0
  345. data/lib/rosett_ai/mcp/prompts/diagnostics_prompt.rb +41 -0
  346. data/lib/rosett_ai/mcp/prompts/validation_prompt.rb +41 -0
  347. data/lib/rosett_ai/mcp/resources/behaviour_resource.rb +127 -0
  348. data/lib/rosett_ai/mcp/resources/config_resource.rb +72 -0
  349. data/lib/rosett_ai/mcp/resources/design_resource.rb +58 -0
  350. data/lib/rosett_ai/mcp/resources/hooks_resource.rb +74 -0
  351. data/lib/rosett_ai/mcp/resources/provenance_resource.rb +51 -0
  352. data/lib/rosett_ai/mcp/resources/rules_resource.rb +60 -0
  353. data/lib/rosett_ai/mcp/resources/schema_resource.rb +72 -0
  354. data/lib/rosett_ai/mcp/response_helper.rb +46 -0
  355. data/lib/rosett_ai/mcp/security_logger.rb +60 -0
  356. data/lib/rosett_ai/mcp/server.rb +212 -0
  357. data/lib/rosett_ai/mcp/settings/server_installer.rb +112 -0
  358. data/lib/rosett_ai/mcp/settings/trust_manager.rb +142 -0
  359. data/lib/rosett_ai/mcp/tools/adopt_tool.rb +70 -0
  360. data/lib/rosett_ai/mcp/tools/backup_tool.rb +64 -0
  361. data/lib/rosett_ai/mcp/tools/behaviour_display_tool.rb +72 -0
  362. data/lib/rosett_ai/mcp/tools/behaviour_list_tool.rb +56 -0
  363. data/lib/rosett_ai/mcp/tools/behaviour_manage_tool.rb +114 -0
  364. data/lib/rosett_ai/mcp/tools/behaviour_show_tool.rb +62 -0
  365. data/lib/rosett_ai/mcp/tools/compile_status_tool.rb +122 -0
  366. data/lib/rosett_ai/mcp/tools/compile_tool.rb +191 -0
  367. data/lib/rosett_ai/mcp/tools/comply_tool.rb +79 -0
  368. data/lib/rosett_ai/mcp/tools/config_compile_tool.rb +71 -0
  369. data/lib/rosett_ai/mcp/tools/config_status_tool.rb +79 -0
  370. data/lib/rosett_ai/mcp/tools/content_tool.rb +78 -0
  371. data/lib/rosett_ai/mcp/tools/context_query_tool.rb +156 -0
  372. data/lib/rosett_ai/mcp/tools/design_list_tool.rb +57 -0
  373. data/lib/rosett_ai/mcp/tools/design_show_tool.rb +69 -0
  374. data/lib/rosett_ai/mcp/tools/doctor_tool.rb +62 -0
  375. data/lib/rosett_ai/mcp/tools/documentation_status_tool.rb +45 -0
  376. data/lib/rosett_ai/mcp/tools/engines_tool.rb +84 -0
  377. data/lib/rosett_ai/mcp/tools/hook_install_tool.rb +190 -0
  378. data/lib/rosett_ai/mcp/tools/hook_preview_tool.rb +173 -0
  379. data/lib/rosett_ai/mcp/tools/hooks_status_tool.rb +84 -0
  380. data/lib/rosett_ai/mcp/tools/init_tool.rb +87 -0
  381. data/lib/rosett_ai/mcp/tools/license_status_tool.rb +44 -0
  382. data/lib/rosett_ai/mcp/tools/project_tool.rb +117 -0
  383. data/lib/rosett_ai/mcp/tools/provenance_tool.rb +97 -0
  384. data/lib/rosett_ai/mcp/tools/provenance_write_tool.rb +40 -0
  385. data/lib/rosett_ai/mcp/tools/retrofit_tool.rb +81 -0
  386. data/lib/rosett_ai/mcp/tools/rule_search_tool.rb +163 -0
  387. data/lib/rosett_ai/mcp/tools/schema_get_tool.rb +94 -0
  388. data/lib/rosett_ai/mcp/tools/tooling_tool.rb +86 -0
  389. data/lib/rosett_ai/mcp/tools/validate_tool.rb +105 -0
  390. data/lib/rosett_ai/mcp/tools/workflow_execute_tool.rb +74 -0
  391. data/lib/rosett_ai/mcp/tools/workflow_tool.rb +78 -0
  392. data/lib/rosett_ai/migration/detector.rb +117 -0
  393. data/lib/rosett_ai/migration/nncc_config_migrator.rb +94 -0
  394. data/lib/rosett_ai/migration/nncc_project_migrator.rb +90 -0
  395. data/lib/rosett_ai/migration/xdg_migrator.rb +123 -0
  396. data/lib/rosett_ai/package_manager/apt.rb +108 -0
  397. data/lib/rosett_ai/package_manager/base.rb +68 -0
  398. data/lib/rosett_ai/package_manager/gem_backend.rb +90 -0
  399. data/lib/rosett_ai/packaging/variant_config.rb +92 -0
  400. data/lib/rosett_ai/path_resolver.rb +115 -0
  401. data/lib/rosett_ai/plugins/contract.rb +43 -0
  402. data/lib/rosett_ai/plugins/engine_contract.rb +60 -0
  403. data/lib/rosett_ai/plugins/gui_contract.rb +74 -0
  404. data/lib/rosett_ai/plugins/mcp_contract.rb +48 -0
  405. data/lib/rosett_ai/plugins/registry.rb +150 -0
  406. data/lib/rosett_ai/policy/auditor.rb +41 -0
  407. data/lib/rosett_ai/policy/deny_list.rb +71 -0
  408. data/lib/rosett_ai/policy/opt_out_scanner.rb +37 -0
  409. data/lib/rosett_ai/policy/policy_compiler.rb +84 -0
  410. data/lib/rosett_ai/policy/protected_files.rb +47 -0
  411. data/lib/rosett_ai/policy/tier_hierarchy.rb +48 -0
  412. data/lib/rosett_ai/policy/validator.rb +35 -0
  413. data/lib/rosett_ai/profiler.rb +79 -0
  414. data/lib/rosett_ai/project/drift_detector.rb +126 -0
  415. data/lib/rosett_ai/project/manager.rb +115 -0
  416. data/lib/rosett_ai/project/sync_manager.rb +138 -0
  417. data/lib/rosett_ai/project/template_applier.rb +105 -0
  418. data/lib/rosett_ai/project_context.rb +82 -0
  419. data/lib/rosett_ai/provenance/entry.rb +63 -0
  420. data/lib/rosett_ai/provenance/file_source.rb +32 -0
  421. data/lib/rosett_ai/provenance/source.rb +62 -0
  422. data/lib/rosett_ai/provenance/store.rb +153 -0
  423. data/lib/rosett_ai/provenance/tracker.rb +62 -0
  424. data/lib/rosett_ai/provenance/trailer_generator.rb +43 -0
  425. data/lib/rosett_ai/provenance/validator.rb +45 -0
  426. data/lib/rosett_ai/quorum/collector.rb +59 -0
  427. data/lib/rosett_ai/quorum/comparator.rb +81 -0
  428. data/lib/rosett_ai/quorum/dispatcher.rb +57 -0
  429. data/lib/rosett_ai/quorum/strategies/adopt.rb +56 -0
  430. data/lib/rosett_ai/rai_config.rb +107 -0
  431. data/lib/rosett_ai/retrofit/base_parser.rb +66 -0
  432. data/lib/rosett_ai/retrofit/engine.rb +171 -0
  433. data/lib/rosett_ai/retrofit/parsers/agents_md_parser.rb +50 -0
  434. data/lib/rosett_ai/retrofit/parsers/claude_parser.rb +69 -0
  435. data/lib/rosett_ai/retrofit/parsers/cursor_parser.rb +82 -0
  436. data/lib/rosett_ai/retrofit/round_trip_validator.rb +65 -0
  437. data/lib/rosett_ai/retrofit/scanner.rb +47 -0
  438. data/lib/rosett_ai/retrofit/secret_detector.rb +87 -0
  439. data/lib/rosett_ai/secrets_resolver.rb +71 -0
  440. data/lib/rosett_ai/smart_feedback/suggester.rb +83 -0
  441. data/lib/rosett_ai/smart_feedback/thor_middleware.rb +84 -0
  442. data/lib/rosett_ai/structured_logger.rb +110 -0
  443. data/lib/rosett_ai/telemetry/json_lines_writer.rb +50 -0
  444. data/lib/rosett_ai/telemetry/log_rotator.rb +67 -0
  445. data/lib/rosett_ai/telemetry/provider.rb +26 -0
  446. data/lib/rosett_ai/telemetry/reporter.rb +144 -0
  447. data/lib/rosett_ai/telemetry.rb +47 -0
  448. data/lib/rosett_ai/text_sanitizer.rb +62 -0
  449. data/lib/rosett_ai/thor/cli.rb +269 -0
  450. data/lib/rosett_ai/thor/tasks/adopt.rb +250 -0
  451. data/lib/rosett_ai/thor/tasks/backup.rb +420 -0
  452. data/lib/rosett_ai/thor/tasks/behaviour.rb +474 -0
  453. data/lib/rosett_ai/thor/tasks/build.rb +1162 -0
  454. data/lib/rosett_ai/thor/tasks/compile.rb +415 -0
  455. data/lib/rosett_ai/thor/tasks/completion.rb +123 -0
  456. data/lib/rosett_ai/thor/tasks/comply.rb +82 -0
  457. data/lib/rosett_ai/thor/tasks/config.rb +265 -0
  458. data/lib/rosett_ai/thor/tasks/content.rb +193 -0
  459. data/lib/rosett_ai/thor/tasks/dbus.rb +321 -0
  460. data/lib/rosett_ai/thor/tasks/design.rb +258 -0
  461. data/lib/rosett_ai/thor/tasks/desktop.rb +129 -0
  462. data/lib/rosett_ai/thor/tasks/doctor.rb +127 -0
  463. data/lib/rosett_ai/thor/tasks/documentation.rb +321 -0
  464. data/lib/rosett_ai/thor/tasks/engines.rb +167 -0
  465. data/lib/rosett_ai/thor/tasks/hooks.rb +219 -0
  466. data/lib/rosett_ai/thor/tasks/init.rb +259 -0
  467. data/lib/rosett_ai/thor/tasks/license.rb +120 -0
  468. data/lib/rosett_ai/thor/tasks/mcp.rb +535 -0
  469. data/lib/rosett_ai/thor/tasks/migrate.rb +121 -0
  470. data/lib/rosett_ai/thor/tasks/plugins.rb +157 -0
  471. data/lib/rosett_ai/thor/tasks/project.rb +260 -0
  472. data/lib/rosett_ai/thor/tasks/provenance.rb +195 -0
  473. data/lib/rosett_ai/thor/tasks/release.rb +314 -0
  474. data/lib/rosett_ai/thor/tasks/retrofit.rb +90 -0
  475. data/lib/rosett_ai/thor/tasks/tooling.rb +308 -0
  476. data/lib/rosett_ai/thor/tasks/validate.rb +108 -0
  477. data/lib/rosett_ai/thor/tasks/workflow.rb +196 -0
  478. data/lib/rosett_ai/tooling/ci_yaml_validator.rb +37 -0
  479. data/lib/rosett_ai/tooling/version_checker.rb +35 -0
  480. data/lib/rosett_ai/ui/accessible_tui.rb +61 -0
  481. data/lib/rosett_ai/ui/base.rb +46 -0
  482. data/lib/rosett_ai/ui/gtk4.rb +98 -0
  483. data/lib/rosett_ai/ui/kde.rb +40 -0
  484. data/lib/rosett_ai/ui/qt6.rb +40 -0
  485. data/lib/rosett_ai/ui/registry.rb +60 -0
  486. data/lib/rosett_ai/ui/tty_helper.rb +74 -0
  487. data/lib/rosett_ai/ui/tui.rb +59 -0
  488. data/lib/rosett_ai/validators/behaviour_validator.rb +20 -0
  489. data/lib/rosett_ai/validators/design_validator.rb +17 -0
  490. data/lib/rosett_ai/validators/schema_validator.rb +84 -0
  491. data/lib/rosett_ai/validators/tooling_validator.rb +17 -0
  492. data/lib/rosett_ai/version.rb +8 -0
  493. data/lib/rosett_ai/version_consistency_checker.rb +129 -0
  494. data/lib/rosett_ai/workflow/audit_log.rb +86 -0
  495. data/lib/rosett_ai/workflow/engine.rb +142 -0
  496. data/lib/rosett_ai/workflow/manager.rb +82 -0
  497. data/lib/rosett_ai/workflow/schema_validator.rb +71 -0
  498. data/lib/rosett_ai/workflow/step_runner.rb +61 -0
  499. data/lib/rosett_ai/workflow/steps/prompt_step.rb +62 -0
  500. data/lib/rosett_ai/workflow/steps/rai_step.rb +74 -0
  501. data/lib/rosett_ai/workflow/steps/shell_step.rb +53 -0
  502. data/lib/rosett_ai/yaml_loader.rb +78 -0
  503. data/lib/rosett_ai.rb +221 -0
  504. data/lib/rubocop/cop/rosett_ai/shell_interpolation.rb +54 -0
  505. data/lib/rubocop/cop/rosett_ai/unsafe_const_get.rb +60 -0
  506. data/lib/rubocop/cop/rosett_ai/unsafe_send.rb +50 -0
  507. data/lib/rubocop/cop/rosett_ai/unsafe_yaml_load.rb +40 -0
  508. data/lib/rubocop/rosett_ai.rb +9 -0
  509. data/lib/scripts/generated/docker_hub_tags.rb +126 -0
  510. data/locales/.gitkeep +0 -0
  511. data/locales/ar.yml +579 -0
  512. data/locales/en.yml +571 -0
  513. data/locales/fr.yml +567 -0
  514. data/packaging/build-engine-deb.sh +81 -0
  515. data/packaging/scripts/postinst +17 -0
  516. data/packaging/scripts/postrm +19 -0
  517. data/packaging/scripts/prerm +10 -0
  518. data/packaging/wrapper.sh.template +38 -0
  519. data/rosett-ai.gemspec +63 -0
  520. data/rules/.gitkeep +0 -0
  521. data/scripts/publish/pulp_upload.sh +123 -0
  522. data/settings.json +29 -0
  523. data/share/applications/be.neatnerds.rosettai.desktop +29 -0
  524. data/share/dbus-1/interfaces/be.neatnerds.rosettai.xml +103 -0
  525. data/share/dbus-1/services/be.neatnerds.rosettai.service +3 -0
  526. data/share/templates/behaviour/criticalthinking.yml +69 -0
  527. metadata +810 -0
@@ -0,0 +1,126 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-License-Identifier: GPL-3.0-only
4
+ # Copyright (C) 2026 Hugo Antonio Sepulveda Manriquez / NeatNerds
5
+
6
+ module RosettAi
7
+ module Completion
8
+ module Shells
9
+ # Generates zsh completion scripts from the Thor command tree.
10
+ #
11
+ # Uses zsh compdef system with _raictl function and _arguments helper.
12
+ # Supports subcommand descriptions shown during tab completion.
13
+ #
14
+ # @author hugo
15
+ # @author claude
16
+ class ZshGenerator
17
+ # @param command_tree [Hash] command tree from Generator
18
+ def initialize(command_tree)
19
+ @command_tree = command_tree
20
+ end
21
+
22
+ # @return [String] zsh completion script
23
+ def generate
24
+ lines = []
25
+ lines << header
26
+ lines << main_function
27
+ lines << subcommand_functions
28
+ lines << registration
29
+ lines.join("\n")
30
+ end
31
+
32
+ private
33
+
34
+ def header
35
+ <<~ZSH
36
+ #compdef rosett-ai
37
+ # zsh completion for Rosett-AI — generated by rai completion zsh
38
+ # Do not edit manually; regenerate with: rai completion zsh
39
+ ZSH
40
+ end
41
+
42
+ def main_function
43
+ <<~ZSH
44
+ _raictl() {
45
+ local -a commands
46
+ commands=(
47
+ #{command_descriptions.chomp}
48
+ )
49
+
50
+ _arguments -C \\
51
+ '1:command:->cmds' \\
52
+ '*::arg:->args'
53
+
54
+ case "$state" in
55
+ cmds)
56
+ _describe -t commands 'rosett-ai command' commands
57
+ ;;
58
+ args)
59
+ case "${words[1]}" in
60
+ #{subcommand_dispatch.chomp}
61
+ esac
62
+ ;;
63
+ esac
64
+ }
65
+ ZSH
66
+ end
67
+
68
+ def command_descriptions
69
+ @command_tree.map do |name, info|
70
+ desc = zsh_escape(info[:description].to_s)
71
+ " '#{zsh_escape(name)}:#{desc}'"
72
+ end.sort.join("\n")
73
+ end
74
+
75
+ def subcommand_dispatch
76
+ lines = []
77
+ @command_tree.each do |name, info|
78
+ subs = info[:subcommands]
79
+ next unless subs
80
+
81
+ lines << " #{zsh_escape(name)})"
82
+ lines << " _rai_#{name.tr('-', '_')}"
83
+ lines << ' ;;'
84
+ end
85
+ lines.join("\n")
86
+ end
87
+
88
+ def subcommand_functions
89
+ functions = []
90
+ @command_tree.each do |name, info|
91
+ subs = info[:subcommands]
92
+ next unless subs
93
+
94
+ func_name = "_rai_#{name.tr('-', '_')}"
95
+ sub_descs = subs.map do |sub_name, sub_info|
96
+ desc = zsh_escape(sub_info[:description].to_s)
97
+ " '#{zsh_escape(sub_name)}:#{desc}'"
98
+ end.sort.join("\n")
99
+
100
+ functions << <<~ZSH
101
+
102
+ #{func_name}() {
103
+ local -a subcommands
104
+ subcommands=(
105
+ #{sub_descs}
106
+ )
107
+ _describe -t subcommands '#{zsh_escape(name)} subcommand' subcommands
108
+ }
109
+ ZSH
110
+ end
111
+ functions.join
112
+ end
113
+
114
+ def registration
115
+ <<~ZSH
116
+ _raictl "$@"
117
+ ZSH
118
+ end
119
+
120
+ def zsh_escape(str)
121
+ str.gsub("'", "'\\''").gsub(/[\\`$"]/) { |c| "\\#{c}" }
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-License-Identifier: GPL-3.0-only
4
+ # Copyright (C) 2026 Hugo Antonio Sepulveda Manriquez / NeatNerds
5
+
6
+ module RosettAi
7
+ module Comply
8
+ module Checkers
9
+ # CRA (Cyber Resilience Act) compliance checker.
10
+ #
11
+ # Verifies SBOM presence, vulnerability disclosure policy,
12
+ # dependency license awareness, and security contact information.
13
+ # All checks work offline without network access.
14
+ #
15
+ # @author hugo
16
+ # @author claude
17
+ class CraChecker
18
+ # @param project_root [Pathname] project root directory
19
+ def initialize(project_root:)
20
+ @project_root = project_root
21
+ end
22
+
23
+ # Runs all CRA compliance checks.
24
+ #
25
+ # @return [Array<Hash>] check results with :id, :description, :status, :message, :remediation
26
+ def check
27
+ [
28
+ check_sbom_present,
29
+ check_vulnerability_policy,
30
+ check_dependency_licenses_known,
31
+ check_security_contact
32
+ ]
33
+ end
34
+
35
+ private
36
+
37
+ def check_sbom_present
38
+ sbom_files = ['sbom.cdx.json', 'sbom.spdx.json'].select do |name|
39
+ @project_root.join(name).exist?
40
+ end
41
+
42
+ if sbom_files.any?
43
+ pass_result('CRA-001', 'SBOM must be present and current',
44
+ "SBOM found: #{sbom_files.join(', ')}")
45
+ else
46
+ fail_result('CRA-001', 'SBOM must be present and current',
47
+ 'No SBOM file found in project root',
48
+ 'Generate an SBOM using cyclonedx-ruby or spdx-sbom-generator and place it in the project root')
49
+ end
50
+ end
51
+
52
+ def check_vulnerability_policy
53
+ security_md = @project_root.join('SECURITY.md')
54
+ if security_md.exist?
55
+ pass_result('CRA-002', 'Vulnerability disclosure policy must be documented',
56
+ 'SECURITY.md found')
57
+ else
58
+ fail_result('CRA-002', 'Vulnerability disclosure policy must be documented',
59
+ 'SECURITY.md not found',
60
+ 'Create SECURITY.md with vulnerability disclosure policy')
61
+ end
62
+ end
63
+
64
+ def check_dependency_licenses_known
65
+ lockfile = @project_root.join('Gemfile.lock')
66
+ unless lockfile.exist?
67
+ return warn_result('CRA-003', 'All dependencies must have known licenses',
68
+ 'No Gemfile.lock found — cannot audit dependencies',
69
+ 'Run bundle install to generate Gemfile.lock')
70
+ end
71
+
72
+ pass_result('CRA-003', 'All dependencies must have known licenses',
73
+ 'Gemfile.lock present — dependency tree available for audit')
74
+ end
75
+
76
+ def check_security_contact
77
+ security_md = @project_root.join('SECURITY.md')
78
+ if security_md.exist? && security_md.read.match?(/contact|report|email/i)
79
+ pass_result('CRA-004', 'Contact information for security reports must be provided',
80
+ 'Security contact information found in SECURITY.md')
81
+ else
82
+ fail_result('CRA-004', 'Contact information for security reports must be provided',
83
+ 'No security contact information found',
84
+ 'Add security contact to SECURITY.md or package metadata')
85
+ end
86
+ end
87
+
88
+ def pass_result(id, description, message)
89
+ { id: id, description: description, status: 'pass', message: message }
90
+ end
91
+
92
+ def warn_result(id, description, message, remediation)
93
+ { id: id, description: description, status: 'warn', message: message, remediation: remediation }
94
+ end
95
+
96
+ def fail_result(id, description, message, remediation)
97
+ { id: id, description: description, status: 'fail', message: message, remediation: remediation }
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-License-Identifier: GPL-3.0-only
4
+ # Copyright (C) 2026 Hugo Antonio Sepulveda Manriquez / NeatNerds
5
+
6
+ module RosettAi
7
+ module Comply
8
+ module Checkers
9
+ # License compliance checker.
10
+ #
11
+ # Verifies GPL-3.0-only compatibility of all dependencies,
12
+ # LICENSE file presence, and SPDX header coverage.
13
+ # Uses Gemfile.lock parsing for offline dependency audit.
14
+ #
15
+ # @author hugo
16
+ # @author claude
17
+ class LicenseChecker
18
+ GPL_COMPATIBLE = [
19
+ 'MIT', 'BSD-2-Clause', 'BSD-3-Clause', 'Apache-2.0', 'ISC', 'Zlib', '0BSD', 'Unlicense', 'LGPL-2.0-only', 'LGPL-2.0-or-later', 'LGPL-2.1-only', 'LGPL-2.1-or-later', 'LGPL-3.0-only', 'LGPL-3.0-or-later', 'GPL-2.0-only', 'GPL-2.0-or-later', 'GPL-3.0-only', 'GPL-3.0-or-later', 'Ruby', 'PSF-2.0', 'Artistic-2.0', 'MPL-2.0'
20
+ ].freeze
21
+
22
+ GPL_INCOMPATIBLE = [
23
+ 'AGPL-3.0-only', 'AGPL-3.0-or-later', 'SSPL-1.0', 'BUSL-1.1', 'Proprietary', 'CPAL-1.0', 'EUPL-1.1', 'EUPL-1.2'
24
+ ].freeze
25
+
26
+ # @param project_root [Pathname] project root directory
27
+ # @param allowlist [Array<String>] gem names exempt from license check
28
+ def initialize(project_root:, allowlist: [])
29
+ @project_root = project_root
30
+ @allowlist = allowlist
31
+ end
32
+
33
+ # Runs all license compliance checks.
34
+ #
35
+ # @return [Array<Hash>] check results
36
+ def check
37
+ [
38
+ check_gpl_compatible,
39
+ check_license_file_present
40
+ ]
41
+ end
42
+
43
+ private
44
+
45
+ def check_gpl_compatible
46
+ gemspec_files = Dir.glob(@project_root.join('*.gemspec').to_s)
47
+ if gemspec_files.empty?
48
+ return skip_result('LIC-001', 'All dependencies must have GPL-3.0-compatible licenses',
49
+ 'No gemspec found — skipping dependency license audit')
50
+ end
51
+
52
+ skip_result('LIC-001', 'All dependencies must have GPL-3.0-compatible licenses',
53
+ 'Deep dependency license audit not yet implemented — structural check only')
54
+ end
55
+
56
+ def check_license_file_present
57
+ license_file = ['LICENSE', 'LICENSE.md', 'LICENSE.txt', 'COPYING'].find do |name|
58
+ @project_root.join(name).exist?
59
+ end
60
+
61
+ if license_file
62
+ pass_result('LIC-002', 'Project LICENSE file must be present',
63
+ "License file found: #{license_file}")
64
+ else
65
+ fail_result('LIC-002', 'Project LICENSE file must be present',
66
+ 'No LICENSE file found in project root',
67
+ 'Create LICENSE file with GPL-3.0-only text')
68
+ end
69
+ end
70
+
71
+ def pass_result(id, description, message)
72
+ { id: id, description: description, status: 'pass', message: message }
73
+ end
74
+
75
+ def skip_result(id, description, message)
76
+ { id: id, description: description, status: 'skip', message: message }
77
+ end
78
+
79
+ def fail_result(id, description, message, remediation)
80
+ { id: id, description: description, status: 'fail', message: message, remediation: remediation }
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-License-Identifier: GPL-3.0-only
4
+ # Copyright (C) 2026 Hugo Antonio Sepulveda Manriquez / NeatNerds
5
+
6
+ module RosettAi
7
+ module Comply
8
+ module Checkers
9
+ # SPDX license header compliance checker.
10
+ #
11
+ # Verifies that all source files contain proper SPDX-License-Identifier
12
+ # headers. Supports Ruby, YAML, JSON, Bash, and Markdown files.
13
+ #
14
+ # @author hugo
15
+ # @author claude
16
+ class SpdxHeaderChecker
17
+ SPDX_PATTERN = /SPDX-License-Identifier:/
18
+ HEADER_SCAN_LINES = 10
19
+
20
+ SOURCE_GLOBS = [
21
+ '**/*.rb',
22
+ '**/*.yml',
23
+ '**/*.yaml',
24
+ '**/*.sh'
25
+ ].freeze
26
+
27
+ SKIP_DIRS = [
28
+ 'vendor',
29
+ 'node_modules',
30
+ 'tmp',
31
+ 'coverage',
32
+ 'pkg',
33
+ '.git'
34
+ ].freeze
35
+
36
+ # @param project_root [Pathname] project root directory
37
+ # @param expected_spdx [String] expected SPDX identifier
38
+ def initialize(project_root:, expected_spdx: 'GPL-3.0-only')
39
+ @project_root = project_root
40
+ @expected_spdx = expected_spdx
41
+ end
42
+
43
+ # Runs SPDX header compliance check.
44
+ #
45
+ # @return [Array<Hash>] check results
46
+ def check
47
+ files = discover_source_files
48
+ return [pass_result_no_files] if files.empty?
49
+
50
+ missing = files.reject { |path| spdx_header?(path) }
51
+
52
+ if missing.empty?
53
+ [pass_result(files.size)]
54
+ else
55
+ [warn_result(missing, files.size)]
56
+ end
57
+ end
58
+
59
+ private
60
+
61
+ def discover_source_files
62
+ all_files = SOURCE_GLOBS.flat_map { |glob| Dir.glob(@project_root.join(glob).to_s) }
63
+ all_files.reject { |path| skip_path?(path) }
64
+ end
65
+
66
+ def skip_path?(path)
67
+ relative = path.delete_prefix("#{@project_root}/")
68
+ SKIP_DIRS.any? { |dir| relative.start_with?("#{dir}/") }
69
+ end
70
+
71
+ def spdx_header?(path)
72
+ File.foreach(path).first(HEADER_SCAN_LINES).any? { |line| SPDX_PATTERN.match?(line) }
73
+ rescue Errno::ENOENT, Errno::EACCES
74
+ false
75
+ end
76
+
77
+ def pass_result_no_files
78
+ { id: 'SPDX-001', description: 'All source files must have SPDX headers',
79
+ status: 'pass', message: 'No source files found to check' }
80
+ end
81
+
82
+ def pass_result(total)
83
+ { id: 'SPDX-001', description: 'All source files must have SPDX headers',
84
+ status: 'pass', message: "All #{total} source files have SPDX headers" }
85
+ end
86
+
87
+ def warn_result(missing, total)
88
+ sample = missing.first(5).map { |path| path.delete_prefix("#{@project_root}/") }
89
+ more = missing.size > 5 ? " (and #{missing.size - 5} more)" : ''
90
+ { id: 'SPDX-001', description: 'All source files must have SPDX headers',
91
+ status: 'warn',
92
+ message: "#{missing.size}/#{total} files missing SPDX header: #{sample.join(', ')}#{more}",
93
+ remediation: 'Add SPDX-License-Identifier header to source files' }
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-License-Identifier: GPL-3.0-only
4
+ # Copyright (C) 2026 Hugo Antonio Sepulveda Manriquez / NeatNerds
5
+
6
+ require 'json'
7
+ require 'rainbow'
8
+ require 'terminal-table'
9
+
10
+ module RosettAi
11
+ module Comply
12
+ # Formats compliance check results for display.
13
+ #
14
+ # TTY-aware: coloured table when interactive, plain text when piped.
15
+ # Supports JSON output mode for CI integration.
16
+ #
17
+ # @author hugo
18
+ # @author claude
19
+ class Reporter
20
+ # @param results [Array<Hash>] check results from Runner
21
+ # @param format [String] output format ('table' or 'json')
22
+ # @param output [IO] output stream (default: $stdout)
23
+ def initialize(results:, format: 'table', output: $stdout)
24
+ @results = results
25
+ @format = format
26
+ @output = output
27
+ end
28
+
29
+ # Renders the compliance report.
30
+ #
31
+ # @return [void]
32
+ def report
33
+ case @format
34
+ when 'json' then report_json
35
+ else report_table
36
+ end
37
+ end
38
+
39
+ # Computes exit code from results.
40
+ #
41
+ # @return [Integer] 0 = all pass, 1 = warnings only, 2 = any failure
42
+ def exit_code
43
+ return 2 if @results.any? { |r| r[:status] == 'fail' }
44
+ return 1 if @results.any? { |r| r[:status] == 'warn' }
45
+
46
+ 0
47
+ end
48
+
49
+ private
50
+
51
+ def report_table
52
+ table = ::Terminal::Table.new(
53
+ headings: ['ID', 'Status', 'Description', 'Details'],
54
+ rows: @results.map { |r| build_row(r) },
55
+ style: { border: :unicode_round }
56
+ )
57
+ @output.puts table
58
+ @output.puts ''
59
+ report_summary
60
+ end
61
+
62
+ def build_row(result)
63
+ status_text = format_status(result[:status])
64
+ detail = result[:message]
65
+ detail = "#{detail}\n Fix: #{result[:remediation]}" if result[:remediation]
66
+ [result[:id], status_text, result[:description], detail]
67
+ end
68
+
69
+ def format_status(status)
70
+ case status
71
+ when 'pass' then Rainbow('PASS').green
72
+ when 'warn' then Rainbow('WARN').yellow
73
+ when 'fail' then Rainbow('FAIL').red
74
+ else status.upcase
75
+ end
76
+ end
77
+
78
+ def report_summary
79
+ counts = @results.group_by { |r| r[:status] }.transform_values(&:size)
80
+ total = @results.size
81
+ pass_count = counts.fetch('pass', 0)
82
+ warn_count = counts.fetch('warn', 0)
83
+ fail_count = counts.fetch('fail', 0)
84
+
85
+ summary = "#{total} checks: #{pass_count} passed, #{warn_count} warnings, #{fail_count} failures"
86
+
87
+ if fail_count.positive?
88
+ @output.puts Rainbow(summary).red
89
+ elsif warn_count.positive?
90
+ @output.puts Rainbow(summary).yellow
91
+ else
92
+ @output.puts Rainbow(summary).green
93
+ end
94
+ end
95
+
96
+ def report_json
97
+ output = {
98
+ compliance: {
99
+ checks: @results,
100
+ summary: {
101
+ total: @results.size,
102
+ pass: @results.count { |r| r[:status] == 'pass' },
103
+ warn: @results.count { |r| r[:status] == 'warn' },
104
+ fail: @results.count { |r| r[:status] == 'fail' }
105
+ },
106
+ exit_code: exit_code
107
+ }
108
+ }
109
+ @output.puts JSON.pretty_generate(output)
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-License-Identifier: GPL-3.0-only
4
+ # Copyright (C) 2026 Hugo Antonio Sepulveda Manriquez / NeatNerds
5
+
6
+ module RosettAi
7
+ module Comply
8
+ # Orchestrates compliance checks across all registered checkers.
9
+ #
10
+ # Supports selective execution via check type filters (--cra, --license,
11
+ # --headers) and aggregates results for the Reporter.
12
+ #
13
+ # @author hugo
14
+ # @author claude
15
+ class Runner
16
+ CHECKER_MAP = {
17
+ cra: Checkers::CraChecker,
18
+ license: Checkers::LicenseChecker,
19
+ headers: Checkers::SpdxHeaderChecker
20
+ }.freeze
21
+
22
+ # @param project_root [Pathname] project root directory
23
+ # @param checks [Array<Symbol>, nil] specific check types to run (nil = all)
24
+ def initialize(project_root:, checks: nil)
25
+ @project_root = project_root
26
+ @checks = checks
27
+ end
28
+
29
+ # Runs all applicable compliance checks.
30
+ #
31
+ # @return [Array<Hash>] aggregated check results
32
+ def run
33
+ active_checkers.flat_map do |checker_class|
34
+ checker = checker_class.new(project_root: @project_root)
35
+ checker.check
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def active_checkers
42
+ if @checks.nil? || @checks.empty?
43
+ CHECKER_MAP.values
44
+ else
45
+ @checks.filter_map { |check| CHECKER_MAP[check] }
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-License-Identifier: GPL-3.0-only
4
+ # Copyright (C) 2026 Hugo Antonio Sepulveda Manriquez / NeatNerds
5
+
6
+ module RosettAi
7
+ module Composition
8
+ # Detects circular dependencies in behaviour depends_on graphs.
9
+ # Uses depth-first search to find cycles and reports the full
10
+ # cycle path for debugging.
11
+ class CircularDependencyDetector
12
+ # Checks a dependency graph for cycles.
13
+ #
14
+ # @param graph [Hash{String => Array<String>}] adjacency list
15
+ # mapping behaviour names to their dependencies
16
+ # @return [Array<String>, nil] cycle path if found, nil otherwise
17
+ def detect(graph)
18
+ visited = {}
19
+ stack = {}
20
+
21
+ graph.each_key do |node|
22
+ next if visited[node]
23
+
24
+ cycle = dfs(node, graph, visited, stack, [])
25
+ return cycle if cycle
26
+ end
27
+
28
+ nil
29
+ end
30
+
31
+ private
32
+
33
+ def dfs(node, graph, visited, stack, path)
34
+ visited[node] = true
35
+ stack[node] = true
36
+ path.push(node)
37
+
38
+ (graph[node] || []).each do |neighbor|
39
+ if stack[neighbor]
40
+ cycle_start = path.index(neighbor)
41
+ return path[cycle_start..] + [neighbor]
42
+ end
43
+
44
+ next if visited[neighbor]
45
+
46
+ result = dfs(neighbor, graph, visited, stack, path)
47
+ return result if result
48
+ end
49
+
50
+ path.pop
51
+ stack.delete(node)
52
+ nil
53
+ end
54
+ end
55
+ end
56
+ end