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,160 @@
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
+ # Centralized MCP tool, resource, and prompt registration.
9
+ #
10
+ # Always loaded before plugins. Registers all built-in tools,
11
+ # resources, and prompts with the MCP server instance.
12
+ #
13
+ # @author hugo
14
+ # @author claude
15
+ module Governance
16
+ TOOL_CLASSES = [
17
+ Tools::ValidateTool,
18
+ Tools::CompileTool,
19
+ Tools::BehaviourListTool,
20
+ Tools::BehaviourShowTool,
21
+ Tools::BehaviourDisplayTool,
22
+ Tools::BehaviourManageTool,
23
+ Tools::DesignListTool,
24
+ Tools::DesignShowTool,
25
+ Tools::ConfigStatusTool,
26
+ Tools::ConfigCompileTool,
27
+ Tools::AdoptTool,
28
+ Tools::ComplyTool,
29
+ Tools::DoctorTool,
30
+ Tools::EnginesTool,
31
+ Tools::HooksStatusTool,
32
+ Tools::LicenseStatusTool,
33
+ Tools::ProjectTool,
34
+ Tools::ProvenanceTool,
35
+ Tools::ProvenanceWriteTool,
36
+ Tools::ToolingTool,
37
+ Tools::WorkflowTool,
38
+ Tools::WorkflowExecuteTool,
39
+ Tools::DocumentationStatusTool,
40
+ Tools::InitTool,
41
+ Tools::BackupTool,
42
+ Tools::ContentTool,
43
+ Tools::RetrofitTool,
44
+ Tools::RuleSearchTool,
45
+ Tools::CompileStatusTool,
46
+ Tools::HookPreviewTool,
47
+ Tools::HookInstallTool,
48
+ Tools::ContextQueryTool,
49
+ Tools::SchemaGetTool
50
+ ].freeze
51
+
52
+ RESOURCE_CLASSES = [
53
+ Resources::BehaviourResource,
54
+ Resources::DesignResource,
55
+ Resources::ProvenanceResource,
56
+ Resources::ConfigResource,
57
+ Resources::SchemaResource,
58
+ Resources::RulesResource,
59
+ Resources::HooksResource
60
+ ].freeze
61
+
62
+ PROMPT_CLASSES = [
63
+ Prompts::ValidationPrompt,
64
+ Prompts::CompilationPrompt,
65
+ Prompts::CompliancePrompt,
66
+ Prompts::DiagnosticsPrompt
67
+ ].freeze
68
+
69
+ KEYWORD_PARAM_TYPES = [:keyreq, :key].freeze
70
+
71
+ module_function
72
+
73
+ # @param server [MCP::Server]
74
+ def register(server)
75
+ register_tools(server)
76
+ register_resources(server)
77
+ register_prompts(server)
78
+ end
79
+
80
+ # @param server [MCP::Server]
81
+ def register_tools(server)
82
+ TOOL_CLASSES.each { |klass| register_tool(server, klass) }
83
+ end
84
+
85
+ # @param server [MCP::Server]
86
+ def register_resources(server)
87
+ resources = RESOURCE_CLASSES.map(&:new)
88
+ server.resources_list_handler { resources.flat_map(&:list) }
89
+ server.resources_read_handler do |uri:|
90
+ resource_name = uri.split('/').last
91
+ resource_class = find_resource_for_uri(resources, uri)
92
+ result = resource_class&.read(resource_name)
93
+ result&.fetch(:content, '')
94
+ end
95
+ end
96
+
97
+ # @param server [MCP::Server]
98
+ def register_prompts(server)
99
+ PROMPT_CLASSES.each do |klass|
100
+ prompt = klass.new
101
+ server.define_prompt(
102
+ name: klass::PROMPT_NAME,
103
+ description: klass::DESCRIPTION
104
+ ) do |args|
105
+ kwargs = (args || {}).transform_keys(&:to_sym)
106
+ prompt.call(**kwargs)
107
+ end
108
+ end
109
+ end
110
+
111
+ # @param server [MCP::Server]
112
+ # @param klass [Class] tool class with TOOL_NAME, DESCRIPTION, ANNOTATIONS
113
+ def register_tool(server, klass)
114
+ schema = klass.const_defined?(:INPUT_SCHEMA) ? klass::INPUT_SCHEMA : nil
115
+ server.define_tool(
116
+ name: klass::TOOL_NAME,
117
+ description: klass::DESCRIPTION,
118
+ annotations: snake_case_annotations(klass::ANNOTATIONS),
119
+ input_schema: schema
120
+ ) do |**args|
121
+ tool = klass.new
122
+ kwargs = Governance.build_kwargs(tool, args)
123
+ result = kwargs.empty? ? tool.call : tool.call(**kwargs)
124
+ MCP::Tool::Response.new([{ type: 'text', text: JSON.generate(result) }])
125
+ end
126
+ end
127
+
128
+ def find_resource_for_uri(resources, uri)
129
+ resources.find { |r| resource_matches_uri?(r, uri) }
130
+ end
131
+
132
+ def resource_matches_uri?(resource, uri)
133
+ resource.list.any? { |entry| entry[:uri] == uri }
134
+ end
135
+
136
+ # Convert camelCase annotation keys to snake_case symbols
137
+ # for the mcp gem's Annotations initializer.
138
+ def snake_case_annotations(hash)
139
+ hash.each_with_object({}) do |(key, value), result|
140
+ snake = key.to_s.gsub(/([a-z])([A-Z])/, '\1_\2').downcase.to_sym
141
+ result[snake] = value
142
+ end
143
+ end
144
+
145
+ # @param tool [Object] tool instance with #call
146
+ # @param args [Hash, nil] MCP arguments
147
+ def build_kwargs(tool, args)
148
+ return {} unless args.is_a?(Hash)
149
+
150
+ method_params = tool.method(:call).parameters
151
+ param_names = method_params.filter_map { |type, name| name if KEYWORD_PARAM_TYPES.include?(type) }
152
+
153
+ args.each_with_object({}) do |(key, value), kwargs|
154
+ sym = key.to_sym
155
+ kwargs[sym] = value if param_names.include?(sym)
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,158 @@
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
+ # Typed HTTP security configuration hierarchy.
9
+ #
10
+ # Seven typed sub-config classes covering authentication, TLS,
11
+ # origin validation, rate limiting, CORS, and content type.
12
+ #
13
+ # @author hugo
14
+ # @author claude
15
+ class HttpSecurityConfig
16
+ attr_accessor :authentication, :tls, :origin, :rate_limiting,
17
+ :max_request_size, :content_type_enforcement, :cors
18
+
19
+ def initialize
20
+ @authentication = AuthConfig.new
21
+ @tls = TlsConfig.new
22
+ @origin = OriginConfig.new
23
+ @rate_limiting = RateLimitConfig.new
24
+ @max_request_size = 1_048_576
25
+ @content_type_enforcement = true
26
+ @cors = CorsConfig.new
27
+ end
28
+
29
+ # Apply a configuration hash from YAML.
30
+ #
31
+ # @param hash [Hash] configuration hash
32
+ # @return [void]
33
+ def apply(hash)
34
+ return unless hash.is_a?(Hash)
35
+
36
+ @authentication.apply(hash['authentication']) if hash['authentication']
37
+ @tls.apply(hash['tls']) if hash['tls']
38
+ @origin.apply(hash['origin']) if hash['origin']
39
+ @rate_limiting.apply(hash['rate_limiting']) if hash['rate_limiting']
40
+ @cors.apply(hash['cors']) if hash['cors']
41
+ @max_request_size = hash['max_request_size'] if hash['max_request_size']
42
+ @content_type_enforcement = hash['content_type_enforcement'] if hash.key?('content_type_enforcement')
43
+ end
44
+
45
+ # Authentication sub-configuration.
46
+ class AuthConfig
47
+ attr_accessor :type, :key_source, :keyfile_path, :api_key_env
48
+
49
+ DEFAULT_KEYFILE_NAME = 'rosett-ai-mcp-keys.yml'
50
+
51
+ def initialize
52
+ @type = 'api_key'
53
+ @key_source = 'auto'
54
+ @keyfile_path = DEFAULT_KEYFILE_NAME
55
+ @api_key_env = 'RAI_MCP_API_KEY'
56
+ end
57
+
58
+ # Resolve the effective key source.
59
+ #
60
+ # @return [String] 'keyfile' or 'env'
61
+ def resolved_key_source
62
+ return @key_source unless @key_source == 'auto'
63
+
64
+ File.exist?(File.expand_path(@keyfile_path)) ? 'keyfile' : 'env'
65
+ end
66
+
67
+ def apply(hash)
68
+ return unless hash.is_a?(Hash)
69
+
70
+ @type = hash['type'] if hash['type']
71
+ @key_source = hash['key_source'] if hash['key_source']
72
+ @keyfile_path = hash['keyfile_path'] if hash['keyfile_path']
73
+ @api_key_env = hash['api_key_env'] if hash['api_key_env']
74
+ end
75
+ end
76
+
77
+ # TLS sub-configuration.
78
+ class TlsConfig
79
+ attr_accessor :enabled, :cert_path, :key_path
80
+
81
+ def initialize
82
+ @enabled = false
83
+ @cert_path = nil
84
+ @key_path = nil
85
+ end
86
+
87
+ def apply(hash)
88
+ return unless hash.is_a?(Hash)
89
+
90
+ @enabled = hash['enabled'] if hash.key?('enabled')
91
+ @cert_path = hash['cert_path'] if hash['cert_path']
92
+ @key_path = hash['key_path'] if hash['key_path']
93
+ end
94
+ end
95
+
96
+ # Origin validation sub-configuration.
97
+ class OriginConfig
98
+ DEFAULT_ORIGINS = ['http://localhost:*', 'http://127.0.0.1:*'].freeze
99
+
100
+ attr_accessor :allowed_origins, :strict_mode
101
+
102
+ def initialize
103
+ @allowed_origins = DEFAULT_ORIGINS.dup
104
+ @strict_mode = false
105
+ end
106
+
107
+ def apply(hash)
108
+ return unless hash.is_a?(Hash)
109
+
110
+ @allowed_origins = hash['allowed_origins'] if hash['allowed_origins']
111
+ @strict_mode = hash['strict_mode'] if hash.key?('strict_mode')
112
+ end
113
+ end
114
+
115
+ # Rate limiting sub-configuration.
116
+ class RateLimitConfig
117
+ attr_accessor :enabled, :unauthenticated_rpm, :authenticated_rpm
118
+
119
+ def initialize
120
+ @enabled = true
121
+ @unauthenticated_rpm = 60
122
+ @authenticated_rpm = 300
123
+ end
124
+
125
+ def apply(hash)
126
+ return unless hash.is_a?(Hash)
127
+
128
+ @enabled = hash['enabled'] if hash.key?('enabled')
129
+ @unauthenticated_rpm = hash['unauthenticated_rpm'] if hash['unauthenticated_rpm']
130
+ @authenticated_rpm = hash['authenticated_rpm'] if hash['authenticated_rpm']
131
+ end
132
+ end
133
+
134
+ # CORS sub-configuration.
135
+ class CorsConfig
136
+ attr_accessor :enabled, :origins, :methods, :headers, :max_age
137
+
138
+ def initialize
139
+ @enabled = false
140
+ @origins = []
141
+ @methods = ['POST', 'GET', 'DELETE']
142
+ @headers = ['Content-Type', 'Authorization', 'Accept', 'Mcp-Session-Id']
143
+ @max_age = 86_400
144
+ end
145
+
146
+ def apply(hash)
147
+ return unless hash.is_a?(Hash)
148
+
149
+ @enabled = hash['enabled'] if hash.key?('enabled')
150
+ @origins = hash['origins'] if hash['origins']
151
+ @methods = hash['methods'] if hash['methods']
152
+ @headers = hash['headers'] if hash['headers']
153
+ @max_age = hash['max_age'] if hash['max_age']
154
+ end
155
+ end
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,266 @@
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
+ # Generates the MCP server instructions markdown for AI client
9
+ # auto-discovery. The instructions field is read by AI clients
10
+ # on session start to understand available tools, resources,
11
+ # and workflows without manual lookup.
12
+ #
13
+ # Content is generated dynamically from the registered tool and
14
+ # resource classes in {Governance}, ensuring documentation-tool
15
+ # parity (DES-RAI-MCP-002 constraint).
16
+ #
17
+ # @author hugo
18
+ # @author claude
19
+ module Instructions
20
+ # Tool categories for the instructions inventory.
21
+ # Maps category name to the tool class constants that belong to it.
22
+ CATEGORIES = {
23
+ 'Discovery' => [
24
+ 'rai_behaviour_list', 'rai_behaviour_show', 'rai_behaviour_display',
25
+ 'rai_design_list', 'rai_design_show', 'rai_documentation_status',
26
+ 'rai_rule_search', 'rai_schema_get', 'rai_compile_status'
27
+ ],
28
+ 'Enforcement' => [
29
+ 'rai_hook_preview', 'rai_hook_install', 'rai_context_query'
30
+ ],
31
+ 'Management' => [
32
+ 'rai_behaviour_manage', 'rai_compile', 'rai_config_compile',
33
+ 'rai_validate', 'rai_init', 'rai_backup', 'rai_content', 'rai_retrofit'
34
+ ],
35
+ 'Compliance' => [
36
+ 'rai_comply', 'rai_provenance', 'rai_provenance_init', 'rai_adopt', 'rai_tooling'
37
+ ],
38
+ 'Operations' => [
39
+ 'rai_doctor', 'rai_project', 'rosett_ai_engines',
40
+ 'rai_hooks_status', 'rai_config_status', 'rai_license_status'
41
+ ],
42
+ 'Workflow' => [
43
+ 'rai_workflow', 'rai_workflow_execute'
44
+ ]
45
+ }.freeze
46
+
47
+ # Resource URI prefixes for the instructions inventory.
48
+ RESOURCE_URIS = {
49
+ 'rosett-ai://behaviour/{name}' => 'Behaviour YAML via 3-tier lookup (supports ?tier=xdg)',
50
+ 'rosett-ai://design/{name}' => 'Design document YAML content',
51
+ 'rosett-ai://provenance/' => 'AI provenance entries',
52
+ 'rosett-ai://config/{scope}' => 'Settings for a scope (managed, user, project, local)',
53
+ 'rosett-ai://schema/{name}' => 'JSON Schema content for validation',
54
+ 'rosett-ai://rules/{name}' => 'Compiled rule markdown content',
55
+ 'rosett-ai://hooks/{scope}' => 'Installed enforcement hook scripts'
56
+ }.freeze
57
+
58
+ module_function
59
+
60
+ # Generates the complete instructions markdown string.
61
+ #
62
+ # @return [String] markdown instructions for AI client consumption
63
+ def generate
64
+ sections = [
65
+ header_section,
66
+ tool_inventory_section,
67
+ resource_section,
68
+ scope_hierarchy_section,
69
+ workflow_section,
70
+ mutation_safety_section
71
+ ]
72
+ sections.join("\n\n")
73
+ end
74
+
75
+ # @return [String] header section
76
+ def header_section
77
+ <<~MARKDOWN.chomp
78
+ # Rosett-AI MCP Server
79
+
80
+ This server exposes rosett-ai configuration management tools via the
81
+ Model Context Protocol. Use this guide to select the right tool for
82
+ your task.
83
+
84
+ Version: #{RosettAi::VERSION}
85
+ MARKDOWN
86
+ end
87
+
88
+ # Generates the tool inventory grouped by category.
89
+ #
90
+ # @return [String] tool inventory markdown
91
+ def tool_inventory_section
92
+ lines = ["## Tool Inventory\n"]
93
+
94
+ CATEGORIES.each do |category, tool_names|
95
+ append_tool_table(lines, category, tool_entries_for(tool_names))
96
+ end
97
+
98
+ uncategorized = uncategorized_tools
99
+ append_tool_table(lines, 'Other', uncategorized) unless uncategorized.empty?
100
+
101
+ lines.join("\n")
102
+ end
103
+
104
+ # Appends a category tool table to the lines array.
105
+ #
106
+ # @param lines [Array<String>] accumulator
107
+ # @param heading [String] category heading
108
+ # @param entries [Array<Hash>] tool entries
109
+ # @return [void]
110
+ def append_tool_table(lines, heading, entries)
111
+ lines << "### #{heading}\n"
112
+ lines << '| Tool | Description | Read-only |'
113
+ lines << '|------|-------------|-----------|'
114
+ entries.each { |e| lines << format_tool_row(e) }
115
+ lines << ''
116
+ end
117
+
118
+ # Formats a single tool entry as a markdown table row.
119
+ #
120
+ # @param entry [Hash] tool entry with :name, :description, :read_only
121
+ # @return [String] markdown table row
122
+ def format_tool_row(entry)
123
+ ro = entry[:read_only] ? 'Yes' : 'No'
124
+ "| `#{entry[:name]}` | #{entry[:description]} | #{ro} |"
125
+ end
126
+
127
+ # Generates the resource URI documentation.
128
+ #
129
+ # @return [String] resource section markdown
130
+ def resource_section
131
+ table_rows = RESOURCE_URIS.map { |uri, desc| "| `#{uri}` | #{desc} |" }
132
+
133
+ [
134
+ "## Resources\n",
135
+ '| URI Pattern | Description |',
136
+ '|-------------|-------------|',
137
+ *table_rows,
138
+ '',
139
+ 'Query parameters:',
140
+ '- `?tier=project|xdg|packaged` — request a specific tier (bypasses merge)',
141
+ '- `?strategy=first_wins|deep_merge|array_union` — override merge strategy'
142
+ ].join("\n")
143
+ end
144
+
145
+ # Explains the scope hierarchy for rule resolution.
146
+ #
147
+ # @return [String] scope hierarchy markdown
148
+ def scope_hierarchy_section
149
+ <<~MARKDOWN.chomp
150
+ ## Scope Hierarchy
151
+
152
+ Rules are resolved in priority order (highest scope wins):
153
+
154
+ 1. **Project** (`.rosett-ai/conf/behaviour/`) — project-specific overrides
155
+ 2. **XDG user** (`~/.config/rosett-ai/conf/behaviour/`) — user-authored global rules
156
+ 3. **Packaged** (`/opt/rosett-ai/app/conf/behaviour/`) — default templates from installation
157
+
158
+ Compiled output goes to `~/.claude/rules/` via `rai_compile`.
159
+ MARKDOWN
160
+ end
161
+
162
+ # Documents common workflows.
163
+ #
164
+ # @return [String] workflow section markdown
165
+ def workflow_section
166
+ <<~MARKDOWN.chomp
167
+ ## Common Workflows
168
+
169
+ ### List and inspect behaviours
170
+
171
+ 1. `rai_behaviour_list` — see all available behaviours
172
+ 2. `rai_behaviour_show(name: "criticalthinking")` — inspect rules
173
+ 3. Read `rosett-ai://behaviour/criticalthinking` — raw YAML content
174
+
175
+ ### Search and query rules
176
+
177
+ 1. `rai_rule_search(keyword: "TMPDIR")` — find rules by keyword
178
+ 2. `rai_context_query(tool_name: "Bash")` — what rules apply?
179
+ 3. `rai_schema_get(schema_name: "behaviour")` — get validation schema
180
+
181
+ ### Compile rules for an AI engine
182
+
183
+ 1. `rai_validate` — check all config files are valid
184
+ 2. `rai_compile(engine: "claude", simulate: true)` — preview output
185
+ 3. `rai_compile(engine: "claude")` — compile to `~/.claude/rules/`
186
+ 4. `rai_compile_status` — check what needs recompilation
187
+
188
+ ### Install enforcement hooks
189
+
190
+ Rules with an `enforcement` block (type: enforceable, pattern, applies_to,
191
+ action) can be compiled into Claude Code PreToolUse hooks:
192
+
193
+ 1. `rai_hook_preview(behaviour_name: "X")` — preview hook script
194
+ 2. Review the output — check which rules are enforceable vs downgraded
195
+ 3. `rai_hook_install(behaviour_name: "X", confirm: true)` — install
196
+
197
+ The enforcement pipeline validates patterns (rejects degenerate patterns
198
+ like `.*`), generates self-contained Ruby scripts, and installs them as
199
+ PreToolUse hooks. Invalid enforceable rules are downgraded to advisory.
200
+
201
+ ### Check project health
202
+
203
+ 1. `rai_doctor` — run diagnostic checks
204
+ 2. `rai_comply` — CRA, license, and SPDX header compliance
205
+ 3. `rai_config_status` — configuration scope status
206
+ MARKDOWN
207
+ end
208
+
209
+ # Documents mutation safety rules.
210
+ #
211
+ # @return [String] mutation safety markdown
212
+ def mutation_safety_section
213
+ <<~MARKDOWN.chomp
214
+ ## Mutation Safety
215
+
216
+ Tools marked as **not read-only** perform writes. Before calling
217
+ mutation tools:
218
+
219
+ 1. Present the action to the user and ask for confirmation
220
+ 2. Prefer `simulate: true` first (where supported) to preview changes
221
+ 3. The server does not enforce confirmation — the AI client must
222
+
223
+ Mutation tools: `rai_compile`, `rai_config_compile`, `rai_behaviour_manage`,
224
+ `rai_init`, `rai_backup`, `rai_content`, `rai_retrofit`,
225
+ `rai_provenance_init`, `rai_workflow_execute`, `rai_hook_install`.
226
+
227
+ `rai_hook_install` requires `confirm: true` and auto-backs up
228
+ existing hooks before writing. Use `rai_hook_preview` first.
229
+ MARKDOWN
230
+ end
231
+
232
+ # Builds tool entries for a list of tool names from Governance.
233
+ #
234
+ # @param tool_names [Array<String>] tool names to look up
235
+ # @return [Array<Hash>] tool entry hashes with :name, :description, :read_only
236
+ def tool_entries_for(tool_names)
237
+ tool_index = build_tool_index
238
+ tool_names.filter_map { |name| tool_index[name] }
239
+ end
240
+
241
+ # Returns tool entries not assigned to any category.
242
+ #
243
+ # @return [Array<Hash>] uncategorized tool entries
244
+ def uncategorized_tools
245
+ all_categorized = CATEGORIES.values.flatten
246
+ tool_index = build_tool_index
247
+ tool_index.except(*all_categorized)
248
+ .values
249
+ end
250
+
251
+ # Builds an index of tool name to entry hash from Governance.
252
+ #
253
+ # @return [Hash{String => Hash}] tool name to entry mapping
254
+ def build_tool_index
255
+ Governance::TOOL_CLASSES.each_with_object({}) do |klass, index|
256
+ name = klass::TOOL_NAME
257
+ index[name] = {
258
+ name: name,
259
+ description: klass::DESCRIPTION,
260
+ read_only: klass::ANNOTATIONS['readOnlyHint'] == true
261
+ }
262
+ end
263
+ end
264
+ end
265
+ end
266
+ end
@@ -0,0 +1,66 @@
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 'openssl'
7
+ require 'securerandom'
8
+
9
+ module RosettAi
10
+ module Mcp
11
+ # Cryptographic key hashing with per-key random salt.
12
+ #
13
+ # Uses SHA-256 with 16-byte random salt. Verification uses
14
+ # constant-time comparison via Rack::Utils.secure_compare.
15
+ #
16
+ # @author hugo
17
+ # @author claude
18
+ module KeyHasher
19
+ SALT_BYTES = 16
20
+ KEY_PREFIX = 'nncc_'
21
+
22
+ module_function
23
+
24
+ # Hash a plaintext API key with a random salt.
25
+ #
26
+ # @param plaintext [String] the plaintext key
27
+ # @return [String] salted hash in format "salt$digest"
28
+ def hash_key(plaintext)
29
+ salt = SecureRandom.hex(SALT_BYTES)
30
+ digest = OpenSSL::Digest::SHA256.hexdigest("#{salt}#{plaintext}")
31
+ "#{salt}$#{digest}"
32
+ end
33
+
34
+ # Verify a plaintext key against a stored hash.
35
+ #
36
+ # @param plaintext [String] the plaintext key to verify
37
+ # @param stored_hash [String] the stored "salt$digest" hash
38
+ # @return [Boolean] true if the key matches
39
+ def verify_key(plaintext, stored_hash)
40
+ salt, expected_digest = stored_hash.split('$', 2)
41
+ return false unless salt && expected_digest
42
+
43
+ actual_digest = OpenSSL::Digest::SHA256.hexdigest("#{salt}#{plaintext}")
44
+ secure_compare(actual_digest, expected_digest)
45
+ end
46
+
47
+ # Generate a new random API key with the Rosett-AI_ prefix.
48
+ #
49
+ # @return [String] a new API key
50
+ def generate_key
51
+ "#{KEY_PREFIX}#{SecureRandom.hex(24)}"
52
+ end
53
+
54
+ # Constant-time string comparison to prevent timing attacks.
55
+ #
56
+ # @param a [String] first string
57
+ # @param b [String] second string
58
+ # @return [Boolean] true if strings match
59
+ def secure_compare(a_str, b_str)
60
+ return false unless a_str.bytesize == b_str.bytesize
61
+
62
+ OpenSSL.fixed_length_secure_compare(a_str, b_str)
63
+ end
64
+ end
65
+ end
66
+ end