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,109 @@
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
+
8
+ module RosettAi
9
+ module Init
10
+ # Registers the rosett-ai MCP server in Claude Code settings.json.
11
+ #
12
+ # Called by `raictl init --global` to enable AI client auto-discovery
13
+ # of rosett-ai tools via the MCP protocol (DES-RAI-MCP-002).
14
+ #
15
+ # The registration is idempotent: if the rosett-ai entry already
16
+ # exists, no changes are made.
17
+ #
18
+ # @author hugo
19
+ # @author claude
20
+ class McpRegistrar
21
+ MCP_SERVER_NAME = 'rosett-ai'
22
+
23
+ # @param settings_path [String] path to Claude Code settings.json
24
+ def initialize(settings_path: nil)
25
+ @settings_path = settings_path || default_settings_path
26
+ end
27
+
28
+ # Registers the rosett-ai MCP server in settings.json.
29
+ #
30
+ # @return [Hash] result with :action (:registered, :already_registered, :skipped)
31
+ # and :path
32
+ def register
33
+ return skip_result('Settings file not found') unless File.exist?(@settings_path)
34
+
35
+ settings = read_settings
36
+ return already_registered_result if registered?(settings)
37
+
38
+ settings['mcpServers'] ||= {}
39
+ settings['mcpServers'][MCP_SERVER_NAME] = server_entry
40
+ write_settings(settings)
41
+
42
+ { action: :registered, path: @settings_path }
43
+ rescue JSON::ParserError => e
44
+ skip_result("Invalid JSON in settings: #{e.message}")
45
+ end
46
+
47
+ private
48
+
49
+ # @return [String] default path to Claude Code settings.json
50
+ def default_settings_path
51
+ File.join(Dir.home, '.claude', 'settings.json')
52
+ end
53
+
54
+ # @return [Hash] the MCP server entry for settings.json
55
+ def server_entry
56
+ {
57
+ 'command' => resolve_command,
58
+ 'args' => ['mcp', 'serve']
59
+ }
60
+ end
61
+
62
+ # Resolves the raictl command path.
63
+ #
64
+ # Checks known install locations in order, falls back to
65
+ # absolute path from `which` so MCP clients without PATH
66
+ # can still find the binary.
67
+ #
68
+ # @return [String] absolute path or command name
69
+ def resolve_command
70
+ ['/usr/local/bin/raictl', '/usr/bin/raictl'].each do |path|
71
+ return path if File.executable?(path)
72
+ end
73
+
74
+ which_result = `which raictl 2>/dev/null`.strip
75
+ return which_result unless which_result.empty?
76
+
77
+ 'raictl'
78
+ end
79
+
80
+ # @param settings [Hash] parsed settings
81
+ # @return [Boolean] true if rosett-ai is already registered
82
+ def registered?(settings)
83
+ settings.dig('mcpServers', MCP_SERVER_NAME).is_a?(Hash)
84
+ end
85
+
86
+ # @return [Hash] parsed settings.json
87
+ def read_settings
88
+ JSON.parse(File.read(@settings_path))
89
+ end
90
+
91
+ # @param settings [Hash] settings to write
92
+ # @return [void]
93
+ def write_settings(settings)
94
+ File.write(@settings_path, "#{JSON.pretty_generate(settings)}\n")
95
+ end
96
+
97
+ # @return [Hash] result for already registered case
98
+ def already_registered_result
99
+ { action: :already_registered, path: @settings_path }
100
+ end
101
+
102
+ # @param reason [String] why registration was skipped
103
+ # @return [Hash] result for skip case
104
+ def skip_result(reason)
105
+ { action: :skipped, path: @settings_path, reason: reason }
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,38 @@
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 'fileutils'
7
+
8
+ module RosettAi
9
+ module Init
10
+ # MCP-facing facade for .rosett-ai/ project structure initialization.
11
+ #
12
+ # Creates the .rosett-ai/ directory with config.yml and conf/ subdirectories.
13
+ #
14
+ # @author hugo
15
+ # @author claude
16
+ class ProjectInitializer
17
+ PROJECT_DIR = '.rosett-ai'
18
+
19
+ # Sets up the .rosett-ai/ project directory structure.
20
+ #
21
+ # @return [Array<String>] created directories
22
+ def setup
23
+ base_dir = File.join(Dir.pwd, PROJECT_DIR)
24
+ subdirs = ['conf', 'conf/behaviour', 'conf/design']
25
+
26
+ created = DirectoryBuilder.create_directories(base_dir, subdirs)
27
+ write_config(base_dir) unless File.exist?(File.join(base_dir, 'config.yml'))
28
+ created
29
+ end
30
+
31
+ private
32
+
33
+ def write_config(base_dir)
34
+ ConfigFileWriter.write_project_config(base_dir)
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,139 @@
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 'jwt'
7
+ require 'jwt/eddsa'
8
+ require 'ed25519'
9
+
10
+ module RosettAi
11
+ module Licensing
12
+ # Decodes and validates Ed25519-signed JWT license keys.
13
+ #
14
+ # The public verification key is safe to embed in source — it can
15
+ # only verify signatures, not forge them. The private signing key
16
+ # is held server-side and NEVER distributed.
17
+ class LicenseKey
18
+ PUBLIC_KEY_HEX = '91f54336c0d42a7c88642b8d7f8bacab3adba4a4d633a33002fc15f676bfd8f0'
19
+ RAI_PREFIX = 'NNCC-'
20
+ GRACE_PERIOD_DAYS = 14
21
+ OFFLINE_GRACE_DAYS = 30
22
+
23
+ attr_reader :claims
24
+
25
+ # @param raw_key [String] JWT license key, optionally prefixed with "NNCC-"
26
+ def initialize(raw_key)
27
+ @raw_key = raw_key
28
+ @claims = nil
29
+ @decoded = false
30
+ end
31
+
32
+ # Decodes and verifies the Ed25519 JWT signature.
33
+ #
34
+ # @return [self]
35
+ # @raise [RosettAi::LicenseError] if signature verification or decoding fails
36
+ def decode
37
+ return self if @decoded
38
+
39
+ stripped = strip_prefix(@raw_key)
40
+ verify_key = Ed25519::VerifyKey.new([PUBLIC_KEY_HEX].pack('H*'))
41
+ decoded = JWT.decode(stripped, verify_key, true, algorithms: ['EdDSA'], verify_expiration: false)
42
+ @claims = decoded.first
43
+ @decoded = true
44
+ self
45
+ rescue Ed25519::VerifyError, JWT::DecodeError, JWT::VerificationError => e
46
+ raise RosettAi::LicenseError, "signature verification failed: #{e.message}"
47
+ rescue ArgumentError, TypeError => e
48
+ raise RosettAi::LicenseError, "invalid key format: #{e.message}"
49
+ end
50
+
51
+ # Whether the license is currently valid (not expired or within grace).
52
+ #
53
+ # @return [Boolean]
54
+ def valid?
55
+ decode
56
+ !expired? || within_grace?
57
+ rescue RosettAi::LicenseError
58
+ false
59
+ end
60
+
61
+ # Returns the license tier.
62
+ #
63
+ # @return [Tier] tier from claims, defaults to community
64
+ def tier
65
+ decode
66
+ Tier.new(claims['tier'] || 'community')
67
+ end
68
+
69
+ # Whether the license has passed its expiration timestamp.
70
+ #
71
+ # @return [Boolean]
72
+ def expired?
73
+ decode
74
+ return false if perpetual?
75
+
76
+ exp = claims['exp']
77
+ return false unless exp
78
+
79
+ Time.now.to_i > exp
80
+ end
81
+
82
+ # Whether the license is perpetual (never expires).
83
+ #
84
+ # @return [Boolean]
85
+ def perpetual?
86
+ decode
87
+ claims['perpetual'] == true
88
+ end
89
+
90
+ # Whether the license tier is subscriber or enterprise.
91
+ #
92
+ # @return [Boolean]
93
+ def subscriber?
94
+ decode
95
+ ['subscriber', 'enterprise'].include?(claims['tier'])
96
+ end
97
+
98
+ # Days remaining in the post-expiry grace period.
99
+ #
100
+ # @return [Integer, nil] days remaining (0 if exhausted, nil if perpetual or no expiry)
101
+ def grace_remaining
102
+ decode
103
+ return nil if perpetual?
104
+
105
+ exp = claims['exp']
106
+ return nil unless exp
107
+
108
+ elapsed = (Time.now.to_i - exp) / 86_400
109
+ return GRACE_PERIOD_DAYS if elapsed.negative?
110
+
111
+ remaining = GRACE_PERIOD_DAYS - elapsed
112
+ [remaining, 0].max
113
+ end
114
+
115
+ # Whether the license is valid for offline use.
116
+ #
117
+ # @return [Boolean]
118
+ def offline_valid?
119
+ decode
120
+ return true if perpetual?
121
+
122
+ exp = claims['exp']
123
+ return true unless exp
124
+
125
+ (Time.now.to_i - exp) < (OFFLINE_GRACE_DAYS * 86_400)
126
+ end
127
+
128
+ private
129
+
130
+ def strip_prefix(key)
131
+ key.start_with?(RAI_PREFIX) ? key[RAI_PREFIX.length..] : key
132
+ end
133
+
134
+ def within_grace?
135
+ grace_remaining&.positive?
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,64 @@
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 Licensing
8
+ # Persists the license key at ~/.config/rosett-ai/license.key with
9
+ # strict file permissions (0600, owned by current uid).
10
+ class LicenseStore
11
+ SAFE_FILE_MODE = 0o600
12
+ SAFE_DIR_MODE = 0o700
13
+
14
+ def save(raw_key)
15
+ ensure_parent_directory!
16
+ File.write(license_path.to_s, raw_key, mode: 'w', perm: SAFE_FILE_MODE)
17
+ validate_permissions!
18
+ end
19
+
20
+ def load
21
+ return nil unless exists?
22
+
23
+ validate_permissions!
24
+ File.read(license_path.to_s).chomp
25
+ end
26
+
27
+ def remove!
28
+ File.delete(license_path.to_s) if exists?
29
+ end
30
+
31
+ def exists?
32
+ license_path.exist?
33
+ end
34
+
35
+ private
36
+
37
+ def license_path
38
+ RosettAi.paths.license_file
39
+ end
40
+
41
+ def ensure_parent_directory!
42
+ dir = license_path.dirname
43
+ return if dir.exist?
44
+
45
+ FileUtils.mkdir_p(dir.to_s, mode: SAFE_DIR_MODE)
46
+ end
47
+
48
+ def validate_permissions!
49
+ stat = File.stat(license_path.to_s)
50
+
51
+ mode = stat.mode & 0o777
52
+ unless mode == SAFE_FILE_MODE
53
+ raise RosettAi::LicenseError,
54
+ "license file permissions #{format('%04o', mode)} are not 0600"
55
+ end
56
+
57
+ return if stat.uid == Process.uid
58
+
59
+ raise RosettAi::LicenseError,
60
+ "license file owned by uid #{stat.uid}, expected #{Process.uid}"
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,60 @@
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 Licensing
8
+ # Orchestrates the full license validation flow:
9
+ # load from store -> decode -> verify signature -> check expiry.
10
+ #
11
+ # Returns a structured result hash. Never raises for expected
12
+ # states (missing license, expired subscription).
13
+ class LicenseValidator
14
+ # Runs the full license validation flow.
15
+ #
16
+ # @return [Hash{Symbol => Object}] result with :valid, :tier, :warnings, :errors keys
17
+ def validate
18
+ store = LicenseStore.new
19
+ return community_result unless store.exists?
20
+
21
+ raw_key = store.load
22
+ return community_result if raw_key.nil? || raw_key.empty?
23
+
24
+ validate_key(raw_key)
25
+ rescue RosettAi::LicenseError => e
26
+ error_result(e.message)
27
+ end
28
+
29
+ private
30
+
31
+ def validate_key(raw_key)
32
+ key = LicenseKey.new(raw_key)
33
+ key.decode
34
+
35
+ result = { valid: true, tier: key.tier, warnings: [], errors: [] }
36
+ add_expiry_warnings(key, result) if key.expired?
37
+ result
38
+ end
39
+
40
+ def add_expiry_warnings(key, result)
41
+ remaining = key.grace_remaining
42
+ if remaining&.positive?
43
+ result[:warnings] << "Subscription expired. Content available for #{remaining} " \
44
+ 'more day(s). Renew to continue receiving updates.'
45
+ else
46
+ result[:warnings] << 'Subscription expired. Premium content frozen at last installed version.'
47
+ result[:valid] = false
48
+ end
49
+ end
50
+
51
+ def community_result
52
+ { valid: true, tier: Tier.new(:community), warnings: [], errors: [] }
53
+ end
54
+
55
+ def error_result(message)
56
+ { valid: false, tier: Tier.new(:community), warnings: [], errors: [message] }
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,42 @@
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 Licensing
8
+ # Represents a license tier with comparable ordering.
9
+ #
10
+ # Tiers control access to premium content pack downloads — they
11
+ # NEVER restrict software functionality (GPL-3.0 compliance).
12
+ class Tier
13
+ include Comparable
14
+
15
+ HIERARCHY = { community: 0, supporter: 1, subscriber: 2, enterprise: 3 }.freeze
16
+ NAMES = HIERARCHY.keys.freeze
17
+
18
+ attr_reader :name, :level
19
+
20
+ def initialize(name)
21
+ sym = name.to_s.downcase.to_sym
22
+ raise RosettAi::LicenseError, "unknown tier: #{name}" unless HIERARCHY.key?(sym)
23
+
24
+ @name = sym
25
+ @level = HIERARCHY[sym]
26
+ end
27
+
28
+ def <=>(other)
29
+ level <=> other.level
30
+ end
31
+
32
+ def entitled_to?(required)
33
+ other = required.is_a?(Tier) ? required : Tier.new(required)
34
+ self >= other
35
+ end
36
+
37
+ def to_s
38
+ name.to_s.capitalize
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,88 @@
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 Mcp
8
+ module Admin
9
+ # Compliance auditor for MCP server configurations.
10
+ #
11
+ # Generates structured audit reports covering configuration
12
+ # provenance, validation status, and transport security.
13
+ # Supports CRA/NIS2/DORA audit documentation requirements.
14
+ #
15
+ # @author hugo
16
+ # @author claude
17
+ class Auditor
18
+ # @param registry [Registry] server registry
19
+ # @param health_checker [HealthChecker] health checker instance
20
+ # @param schema_validator [SchemaValidator] schema validator instance
21
+ def initialize(registry:, health_checker:, schema_validator:)
22
+ @registry = registry
23
+ @health_checker = health_checker
24
+ @schema_validator = schema_validator
25
+ end
26
+
27
+ # Generates a full audit report.
28
+ #
29
+ # @return [Hash] audit report with :timestamp, :servers, :summary
30
+ def audit
31
+ servers = @registry.list
32
+ health_results = @health_checker.check_all(@registry)
33
+ health_map = health_results.to_h { |r| [r[:name], r] }
34
+
35
+ entries = servers.map { |server| audit_server(server, health_map) }
36
+
37
+ {
38
+ timestamp: Time.now.utc.iso8601,
39
+ servers: entries,
40
+ summary: build_summary(entries)
41
+ }
42
+ end
43
+
44
+ private
45
+
46
+ def audit_server(server, health_map)
47
+ health = health_map[server[:name]] || { status: :unknown }
48
+ validation = @schema_validator.validate(server)
49
+
50
+ {
51
+ name: server[:name],
52
+ transport: server[:transport],
53
+ health_status: health[:status].to_s,
54
+ schema_valid: validation[:valid],
55
+ validation_errors: validation[:errors],
56
+ source_file: server[:source_file],
57
+ transport_security: assess_transport_security(server),
58
+ audited_at: Time.now.utc.iso8601
59
+ }
60
+ end
61
+
62
+ def assess_transport_security(server)
63
+ case server[:transport]
64
+ when 'stdio'
65
+ 'local-only'
66
+ when 'http', 'streamable-http'
67
+ url = server[:url].to_s
68
+ url.start_with?('https://') ? 'tls-encrypted' : 'plaintext-warning'
69
+ else
70
+ 'unknown'
71
+ end
72
+ end
73
+
74
+ SECURE_TRANSPORTS = ['local-only', 'tls-encrypted'].freeze
75
+ private_constant :SECURE_TRANSPORTS
76
+
77
+ def build_summary(entries)
78
+ {
79
+ total: entries.size,
80
+ healthy: entries.count { |e| e[:health_status] == 'available' },
81
+ schema_valid: entries.count { |e| e[:schema_valid] },
82
+ secure_transport: entries.count { |e| SECURE_TRANSPORTS.include?(e[:transport_security]) }
83
+ }
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,81 @@
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 Mcp
8
+ module Admin
9
+ # Health checker for configured MCP servers.
10
+ #
11
+ # Probes each server with a configurable timeout (default 5s)
12
+ # to verify availability and capability negotiation.
13
+ #
14
+ # @author hugo
15
+ # @author claude
16
+ class HealthChecker
17
+ DEFAULT_TIMEOUT = 5
18
+
19
+ # @param timeout [Integer] maximum seconds per health check
20
+ def initialize(timeout: DEFAULT_TIMEOUT)
21
+ @timeout = timeout
22
+ end
23
+
24
+ # Checks health of a single server.
25
+ #
26
+ # @param server [Hash] server entry from Registry
27
+ # @return [Hash] result with :name, :status, :transport, :message
28
+ def check(server)
29
+ case server[:transport]
30
+ when 'stdio'
31
+ check_stdio(server)
32
+ when 'http', 'streamable-http'
33
+ check_http(server)
34
+ else
35
+ { name: server[:name], status: :unknown, transport: server[:transport],
36
+ message: "Unknown transport: #{server[:transport]}" }
37
+ end
38
+ end
39
+
40
+ # Checks health of all servers in a registry.
41
+ #
42
+ # @param registry [Registry] server registry
43
+ # @return [Array<Hash>] health results
44
+ def check_all(registry)
45
+ registry.list.map { |server| check(server) }
46
+ end
47
+
48
+ private
49
+
50
+ def check_stdio(server)
51
+ command = server[:command]
52
+ unless command && command_exists?(command)
53
+ return { name: server[:name], status: :unavailable, transport: 'stdio',
54
+ message: "Command not found: #{command}" }
55
+ end
56
+
57
+ { name: server[:name], status: :available, transport: 'stdio',
58
+ message: 'Command found' }
59
+ end
60
+
61
+ def check_http(server)
62
+ url = server[:url]
63
+ unless url
64
+ return { name: server[:name], status: :unavailable, transport: server[:transport],
65
+ message: 'No URL configured' }
66
+ end
67
+
68
+ { name: server[:name], status: :available, transport: server[:transport],
69
+ message: "URL configured: #{url}" }
70
+ end
71
+
72
+ def command_exists?(command)
73
+ cmd = command.is_a?(Array) ? command.first : command.split.first
74
+ ENV.fetch('PATH', '').split(File::PATH_SEPARATOR).any? do |dir|
75
+ File.executable?(File.join(dir, cmd))
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end