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,103 @@
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 'dbus'
7
+
8
+ module RosettAi
9
+ module DBus
10
+ # D-Bus interface for focus monitoring.
11
+ #
12
+ # Interface: be.neatnerds.rosettai.FocusMonitor
13
+ # Methods:
14
+ # GetCurrentFocus() -> (ss) Return (app_id, window_title)
15
+ # Signals:
16
+ # FocusChanged(app_id: s, title: s) Emitted on active window change
17
+ class FocusMonitorInterface < ::DBus::Object
18
+ INTERFACE = 'be.neatnerds.rosettai.FocusMonitor'
19
+
20
+ dbus_interface INTERFACE do
21
+ dbus_method :GetCurrentFocus, 'out app_id:s, out title:s' do
22
+ get_current_focus
23
+ end
24
+
25
+ dbus_signal :FocusChanged, 'app_id:s, title:s'
26
+ end
27
+
28
+ def initialize(path)
29
+ super
30
+ @current_app_id = ''
31
+ @current_title = ''
32
+ @adapter = nil
33
+ @monitor_thread = nil
34
+ end
35
+
36
+ # Start focus monitoring
37
+ def start
38
+ compositor = CompositorDetector.detect
39
+ RosettAi.logger.info("Detected compositor: #{compositor}")
40
+
41
+ @adapter = create_adapter(compositor)
42
+ return unless @adapter
43
+
44
+ @adapter.on_focus_change do |app_id, title|
45
+ handle_focus_change(app_id, title)
46
+ end
47
+
48
+ @adapter.start
49
+ end
50
+
51
+ # Stop focus monitoring
52
+ def stop
53
+ @adapter&.stop
54
+ @adapter = nil
55
+ end
56
+
57
+ private
58
+
59
+ # Get current focus
60
+ #
61
+ # @return [Array<String, String>] [app_id, title]
62
+ def get_current_focus
63
+ [@current_app_id, @current_title]
64
+ end
65
+
66
+ # Handle focus change event
67
+ #
68
+ # @param app_id [String] Application identifier
69
+ # @param title [String] Window title
70
+ def handle_focus_change(app_id, title)
71
+ return if app_id == @current_app_id && title == @current_title
72
+
73
+ @current_app_id = app_id
74
+ @current_title = title
75
+
76
+ RosettAi.logger.debug("Focus changed: #{app_id} - #{title}")
77
+ FocusChanged(app_id, title)
78
+ end
79
+
80
+ # Create appropriate adapter for the compositor
81
+ #
82
+ # @param compositor [Symbol] Compositor type
83
+ # @return [FocusAdapters::Base, nil]
84
+ def create_adapter(compositor)
85
+ case compositor
86
+ when :sway, :i3
87
+ FocusAdapters::I3Adapter.new
88
+ when :hyprland
89
+ FocusAdapters::HyprlandAdapter.new
90
+ when :gnome
91
+ FocusAdapters::GnomeAdapter.new
92
+ when :kwin
93
+ FocusAdapters::KwinAdapter.new
94
+ when :x11
95
+ FocusAdapters::X11Adapter.new
96
+ else
97
+ RosettAi.logger.warn("No focus adapter for compositor: #{compositor}")
98
+ nil
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,213 @@
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 'dbus'
7
+
8
+ module RosettAi
9
+ module DBus
10
+ # D-Bus interface for Rosett-AI management operations.
11
+ #
12
+ # Interface: be.neatnerds.rosettai.Manager
13
+ # Methods:
14
+ # Compile(engine: s) -> s Run compilation for specified engine
15
+ # SwitchContext(name: s) Switch to named context
16
+ # GetStatus() -> a{sv} Return service status dictionary
17
+ # Properties:
18
+ # Version: s (read) Service version string
19
+ # Signals:
20
+ # ContextChanged(name: s) Emitted after context switch
21
+ #
22
+ # Security:
23
+ # - Rate limiting (10 req/s, burst 20) prevents DoS via rapid method calls
24
+ # - Compile operations have stricter limits (2 req/s, burst 5)
25
+ class ManagerInterface < ::DBus::Object
26
+ INTERFACE = 'be.neatnerds.rosettai.Manager'
27
+
28
+ # Rate limit configurations per operation type
29
+ RATE_LIMITS = {
30
+ default: { rate: 10, burst: 20 },
31
+ compile: { rate: 2, burst: 5 }, # Compile is expensive
32
+ config: { rate: 5, burst: 10 } # Config changes need moderation
33
+ }.freeze
34
+
35
+ # Keys allowed via D-Bus SetConfig — prevents arbitrary config mutation
36
+ SETCONFIG_ALLOWED_KEYS = [
37
+ 'default_engine',
38
+ 'cache.enabled',
39
+ 'cache.ttl_hours',
40
+ 'detect.on_init'
41
+ ].freeze
42
+
43
+ dbus_interface INTERFACE do
44
+ dbus_method :Compile, 'in engine:s, out result:s' do |engine|
45
+ compile(engine)
46
+ end
47
+
48
+ dbus_method :SwitchContext, 'in context_name:s' do |context_name|
49
+ switch_context(context_name)
50
+ end
51
+
52
+ dbus_method :GetStatus, 'out status:a{sv}' do
53
+ get_status
54
+ end
55
+
56
+ dbus_method :Shutdown, '' do
57
+ shutdown
58
+ end
59
+
60
+ dbus_method :SetConfig, 'in key:s, in value:s, out result:s' do |key, value|
61
+ set_config(key, value)
62
+ end
63
+
64
+ dbus_method :ListEngines, 'out engines:a(ss)' do
65
+ list_engines
66
+ end
67
+
68
+ dbus_attr_reader :version, 's'
69
+
70
+ dbus_signal :ContextChanged, 'context_name:s'
71
+ end
72
+
73
+ attr_writer :service
74
+
75
+ def initialize(path)
76
+ super
77
+ @current_context = 'default'
78
+ @service = nil
79
+ @rate_limiters = build_rate_limiters
80
+ end
81
+
82
+ # Version property
83
+ #
84
+ # @return [String]
85
+ def version
86
+ RosettAi::VERSION
87
+ end
88
+
89
+ private
90
+
91
+ # Run compilation for the specified engine
92
+ #
93
+ # @param engine [String] Engine name (claude, generic, agents_md, etc.)
94
+ # @return [String] Compilation result message
95
+ def compile(engine)
96
+ rate_limit!(:compile, 'Compile')
97
+ RosettAi.logger.info("D-Bus compile requested for engine: #{engine}")
98
+
99
+ # Use the compilation pipeline
100
+ backend = RosettAi::Compiler::Backend.for(engine)
101
+ pipeline = RosettAi::Compiler::CompilationPipeline.new(
102
+ source_dir: RosettAi.root.join('conf'),
103
+ target_dir: resolve_target_dir(backend),
104
+ schema_dir: RosettAi.root.join('conf', 'schemas'),
105
+ backend: backend
106
+ )
107
+
108
+ results = pipeline.compile
109
+ count = results.size
110
+ "Compiled #{count} file(s) successfully"
111
+ rescue RosettAi::CompileError => e
112
+ "Compilation failed: #{e.message}"
113
+ rescue StandardError => e
114
+ RosettAi.logger.error("Compile error: #{e.message}")
115
+ "Error: #{e.message}"
116
+ end
117
+
118
+ # Switch to named context
119
+ #
120
+ # @param context_name [String] Context name to switch to
121
+ def switch_context(context_name)
122
+ rate_limit!(:default, 'SwitchContext')
123
+ RosettAi.logger.info("D-Bus context switch: #{@current_context} -> #{context_name}")
124
+ @current_context = context_name
125
+
126
+ # Emit signal
127
+ ContextChanged(context_name)
128
+ end
129
+
130
+ # Get service status
131
+ #
132
+ # @return [Hash] Status dictionary
133
+ def get_status
134
+ rate_limit!(:default, 'GetStatus')
135
+ {
136
+ 'version' => RosettAi::VERSION,
137
+ 'running' => true,
138
+ 'current_context' => @current_context,
139
+ 'pid' => Process.pid,
140
+ 'dbus_name' => Service::BUS_NAME
141
+ }
142
+ end
143
+
144
+ # Initiate graceful service shutdown.
145
+ # Thread is required to avoid blocking the D-Bus event loop during shutdown.
146
+ def shutdown
147
+ RosettAi.logger.info('D-Bus shutdown requested')
148
+ Thread.new do
149
+ sleep(0.1)
150
+ @service&.stop
151
+ end
152
+ nil
153
+ end
154
+
155
+ # Update a configuration key via RaiConfig
156
+ #
157
+ # @param key [String] dot-separated config key (e.g. 'default_engine')
158
+ # @param value [String] new value
159
+ # @return [String] result message
160
+ def set_config(key, value)
161
+ rate_limit!(:config, 'SetConfig')
162
+ return reject_disallowed_key(key) unless SETCONFIG_ALLOWED_KEYS.include?(key)
163
+
164
+ RosettAi.logger.info("D-Bus SetConfig: #{key} = #{value}")
165
+ RosettAi.rai_config.update(key, value)
166
+ "OK: #{key} = #{value}"
167
+ rescue StandardError => e
168
+ RosettAi.logger.error("SetConfig error: #{e.message}")
169
+ "Error: #{e.message}"
170
+ end
171
+
172
+ # List available engines with display names.
173
+ #
174
+ # @return [Array<Array(String, String)>] [name, display_name] tuples
175
+ def list_engines
176
+ rate_limit!(:default, 'ListEngines')
177
+ RosettAi::Engines::Registry.available.map do |name|
178
+ manifest = RosettAi::Engines::Registry.manifest(name)
179
+ [name, manifest['display_name'] || name]
180
+ end
181
+ rescue StandardError => e
182
+ RosettAi.logger.error("ListEngines error: #{e.message}")
183
+ []
184
+ end
185
+
186
+ def reject_disallowed_key(key)
187
+ RosettAi.logger.warn("D-Bus SetConfig rejected disallowed key: #{key}")
188
+ "Error: key '#{key}' is not allowed via D-Bus (permitted: #{SETCONFIG_ALLOWED_KEYS.join(', ')})"
189
+ end
190
+
191
+ def resolve_target_dir(backend)
192
+ dir = backend.profile.output_dir
193
+ dir ? Pathname.new(File.expand_path(dir)) : RosettAi.paths.rules_dir
194
+ end
195
+
196
+ def build_rate_limiters
197
+ RATE_LIMITS.transform_values do |config|
198
+ RateLimiter.new(rate: config[:rate], burst: config[:burst])
199
+ end
200
+ end
201
+
202
+ def rate_limit!(category, method_name)
203
+ return unless @rate_limiters # Skip rate limiting in tests without full init
204
+
205
+ limiter = @rate_limiters[category] || @rate_limiters[:default]
206
+ limiter.consume!(method_name)
207
+ rescue RateLimiter::RateLimitExceeded => e
208
+ RosettAi.logger.warn("D-Bus rate limit exceeded: #{e.message}")
209
+ raise ::DBus::Error, "org.freedesktop.DBus.Error.LimitsExceeded: #{e.message}"
210
+ end
211
+ end
212
+ end
213
+ end
@@ -0,0 +1,169 @@
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 'dbus'
7
+
8
+ module RosettAi
9
+ module DBus
10
+ # D-Bus interface for plugin management.
11
+ #
12
+ # Interface: be.neatnerds.rosettai.PluginManager
13
+ # Methods:
14
+ # ListPlugins(type: s) -> a(sss) Return array of [name, display_name, version]
15
+ # InstallPlugin(type: s, name: s) -> (bs) Install a plugin
16
+ # RemovePlugin(type: s, name: s) -> (bs) Remove a plugin
17
+ # Signals:
18
+ # PluginInstalled(type: s, name: s, version: s)
19
+ # PluginRemoved(type: s, name: s)
20
+ #
21
+ # @author hugo
22
+ # @author claude
23
+ class PluginManagerInterface < ::DBus::Object
24
+ INTERFACE = 'be.neatnerds.rosettai.PluginManager'
25
+
26
+ VALID_TYPES = ['engine', 'gui', 'mcp'].freeze
27
+
28
+ dbus_interface INTERFACE do
29
+ dbus_method :ListPlugins, 'in type:s, out plugins:aas' do |type|
30
+ list_plugins(type)
31
+ end
32
+
33
+ dbus_method :InstallPlugin, 'in type:s, in name:s, out success:b, out message:s' do |type, name|
34
+ install_plugin(type, name)
35
+ end
36
+
37
+ dbus_method :RemovePlugin, 'in type:s, in name:s, out success:b, out message:s' do |type, name|
38
+ remove_plugin(type, name)
39
+ end
40
+
41
+ dbus_signal :PluginInstalled, 'type:s, name:s, version:s'
42
+ dbus_signal :PluginRemoved, 'type:s, name:s'
43
+ end
44
+
45
+ private
46
+
47
+ # List plugins of a given type from the registry.
48
+ #
49
+ # @param type [String] plugin type ('engine', 'gui', 'mcp')
50
+ # @return [Array<Array<String>>] array of [name, display_name, version] tuples
51
+ def list_plugins(type)
52
+ validate_type!(type)
53
+ type_sym = type.to_sym
54
+ plugins = RosettAi::Plugins::Registry.available(type_sym)
55
+
56
+ [plugins.map do |name|
57
+ mod = RosettAi::Plugins::Registry.plugin_module(type_sym, name)
58
+ [name, mod.display_name, mod.version]
59
+ end]
60
+ rescue StandardError => e
61
+ RosettAi.logger.error("ListPlugins failed: #{e.message}")
62
+ [[]]
63
+ end
64
+
65
+ # Install a plugin using the appropriate package manager.
66
+ #
67
+ # @param type [String] plugin type
68
+ # @param name [String] plugin name
69
+ # @return [Array(Boolean, String)] [success, message]
70
+ def install_plugin(type, name)
71
+ authorize_plugin_management!
72
+ validate_type!(type)
73
+ validate_name!(name)
74
+ RosettAi.logger.warn("D-Bus plugin install: #{type}/#{name}")
75
+
76
+ pm = select_package_manager
77
+ package_name = plugin_package_name(type, name)
78
+
79
+ if pm.install(package_name)
80
+ RosettAi::Plugins::Registry.reset!
81
+ RosettAi::Plugins::Registry.discover!
82
+ self.PluginInstalled(type, name, 'installed')
83
+ [true, "Installed #{package_name}"]
84
+ else
85
+ [false, "Failed to install #{package_name}"]
86
+ end
87
+ rescue ::DBus::Error
88
+ raise
89
+ rescue PackageManager::Base::PackageManagerError => e
90
+ [false, e.message]
91
+ rescue StandardError => e
92
+ RosettAi.logger.error("InstallPlugin failed: #{e.message}")
93
+ [false, "Error: #{e.message}"]
94
+ end
95
+
96
+ # Remove a plugin using the appropriate package manager.
97
+ #
98
+ # @param type [String] plugin type
99
+ # @param name [String] plugin name
100
+ # @return [Array(Boolean, String)] [success, message]
101
+ def remove_plugin(type, name)
102
+ authorize_plugin_management!
103
+ validate_type!(type)
104
+ validate_name!(name)
105
+ RosettAi.logger.warn("D-Bus plugin remove: #{type}/#{name}")
106
+
107
+ pm = select_package_manager
108
+ package_name = plugin_package_name(type, name)
109
+
110
+ if pm.remove(package_name)
111
+ RosettAi::Plugins::Registry.reset!
112
+ RosettAi::Plugins::Registry.discover!
113
+ self.PluginRemoved(type, name)
114
+ [true, "Removed #{package_name}"]
115
+ else
116
+ [false, "Failed to remove #{package_name}"]
117
+ end
118
+ rescue ::DBus::Error
119
+ raise
120
+ rescue PackageManager::Base::PackageManagerError => e
121
+ [false, e.message]
122
+ rescue StandardError => e
123
+ RosettAi.logger.error("RemovePlugin failed: #{e.message}")
124
+ [false, "Error: #{e.message}"]
125
+ end
126
+
127
+ # Verify that plugin management via D-Bus is explicitly enabled in config.
128
+ #
129
+ # @raise [::DBus::Error] when dbus.allow_plugin_management is not true
130
+ def authorize_plugin_management!
131
+ return if RosettAi.rai_config.dbus_allow_plugin_management?
132
+
133
+ raise ::DBus::Error,
134
+ 'be.neatnerds.rosettai.Error.NotAuthorized: Plugin management via D-Bus is disabled. ' \
135
+ 'Set dbus.allow_plugin_management: true in ~/.config/rosett-ai/config.yml'
136
+ end
137
+
138
+ def validate_type!(type)
139
+ return if VALID_TYPES.include?(type)
140
+
141
+ raise ArgumentError, "Invalid plugin type: #{type}. Valid: #{VALID_TYPES.join(', ')}"
142
+ end
143
+
144
+ def validate_name!(name)
145
+ return if name.match?(/\A[a-z0-9][a-z0-9_-]*\z/)
146
+
147
+ raise ArgumentError, "Invalid plugin name: #{name}"
148
+ end
149
+
150
+ # Map plugin type + name to a package name.
151
+ # Convention: rosett-ai-<type>-<name> for apt, rosett-ai-<type>-<name> for gems.
152
+ #
153
+ # @return [String]
154
+ def plugin_package_name(type, name)
155
+ "rosett-ai-#{type}-#{name}"
156
+ end
157
+
158
+ # Select the best available package manager.
159
+ #
160
+ # @return [PackageManager::Base]
161
+ def select_package_manager
162
+ apt = PackageManager::Apt.new
163
+ return apt if apt.available?
164
+
165
+ PackageManager::GemBackend.new
166
+ end
167
+ end
168
+ end
169
+ end
@@ -0,0 +1,89 @@
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 DBus
8
+ # Token bucket rate limiter for D-Bus method calls.
9
+ #
10
+ # Provides application-level rate limiting to prevent resource exhaustion
11
+ # from rapid D-Bus method invocations. Each method type can have its own
12
+ # rate limit.
13
+ #
14
+ # Thread-safe: uses mutex for bucket state.
15
+ #
16
+ # @example
17
+ # limiter = RateLimiter.new(rate: 10, burst: 20)
18
+ # if limiter.allow?
19
+ # # process request
20
+ # else
21
+ # raise RateLimitExceeded
22
+ # end
23
+ class RateLimiter
24
+ # Raised when rate limit is exceeded
25
+ class RateLimitExceeded < RosettAi::Error
26
+ def initialize(method_name = nil)
27
+ msg = method_name ? "Rate limit exceeded for #{method_name}" : 'Rate limit exceeded'
28
+ super(msg)
29
+ end
30
+ end
31
+
32
+ # Default limits (requests per second / burst capacity)
33
+ DEFAULT_RATE = 10
34
+ DEFAULT_BURST = 20
35
+
36
+ # @param rate [Numeric] tokens added per second
37
+ # @param burst [Integer] maximum bucket capacity
38
+ def initialize(rate: DEFAULT_RATE, burst: DEFAULT_BURST)
39
+ @rate = rate.to_f
40
+ @burst = burst
41
+ @tokens = burst.to_f
42
+ @last_update = Process.clock_gettime(Process::CLOCK_MONOTONIC)
43
+ @mutex = Mutex.new
44
+ end
45
+
46
+ # Check if a request is allowed and consume a token if so.
47
+ #
48
+ # @return [Boolean] true if request is allowed
49
+ def allow?
50
+ @mutex.synchronize do
51
+ refill_tokens
52
+ if @tokens >= 1.0
53
+ @tokens -= 1.0
54
+ true
55
+ else
56
+ false
57
+ end
58
+ end
59
+ end
60
+
61
+ # Consume a token or raise RateLimitExceeded.
62
+ #
63
+ # @param method_name [String, nil] optional method name for error message
64
+ # @raise [RateLimitExceeded] if no tokens available
65
+ def consume!(method_name = nil)
66
+ raise RateLimitExceeded, method_name unless allow?
67
+ end
68
+
69
+ # Current available tokens (for monitoring/debugging).
70
+ #
71
+ # @return [Float] current token count
72
+ def available_tokens
73
+ @mutex.synchronize do
74
+ refill_tokens
75
+ @tokens
76
+ end
77
+ end
78
+
79
+ private
80
+
81
+ def refill_tokens
82
+ now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
83
+ elapsed = now - @last_update
84
+ @tokens = [(@tokens + (elapsed * @rate)), @burst.to_f].min
85
+ @last_update = now
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,121 @@
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 'dbus'
7
+
8
+ module RosettAi
9
+ module DBus
10
+ # Main D-Bus session service for Rosett-AI.
11
+ #
12
+ # Exports interfaces under be.neatnerds.rosettai bus name:
13
+ # - be.neatnerds.rosettai.Manager: compile, context switching, status
14
+ # - be.neatnerds.rosettai.FocusMonitor: active window tracking
15
+ # - org.kde.StatusNotifierItem: system tray presence
16
+ #
17
+ # The service is designed to gracefully degrade when D-Bus is unavailable.
18
+ class Service
19
+ BUS_NAME = 'be.neatnerds.rosettai'
20
+ OBJECT_PATH = '/be/neatnerds/rosett-ai'
21
+
22
+ attr_reader :bus, :main_loop, :manager, :focus_monitor, :status_notifier
23
+
24
+ def initialize
25
+ @running = false
26
+ @main_loop = nil
27
+ end
28
+
29
+ # Start the D-Bus service
30
+ #
31
+ # @return [Boolean] true if started successfully
32
+ def start
33
+ return false if @running
34
+
35
+ connect_to_session_bus
36
+ export_interfaces
37
+ request_bus_name
38
+ @running = true
39
+ true
40
+ rescue ::DBus::Error => e
41
+ RosettAi.logger.error("D-Bus service failed to start: #{e.message}")
42
+ false
43
+ end
44
+
45
+ # Run the event loop (blocking)
46
+ def run
47
+ return unless @running
48
+
49
+ @main_loop = ::DBus::Main.new
50
+ @main_loop << @bus
51
+ RosettAi.logger.info("D-Bus service running on #{BUS_NAME}")
52
+ @main_loop.run
53
+ rescue Interrupt
54
+ stop
55
+ end
56
+
57
+ # Stop the D-Bus service
58
+ def stop
59
+ return unless @running
60
+
61
+ @main_loop&.quit
62
+ @focus_monitor&.stop
63
+ @running = false
64
+ RosettAi.logger.info('D-Bus service stopped')
65
+ end
66
+
67
+ # Check if the service is running
68
+ #
69
+ # @return [Boolean]
70
+ def running?
71
+ @running
72
+ end
73
+
74
+ # Check if D-Bus session bus is available
75
+ #
76
+ # @return [Boolean]
77
+ def self.dbus_available?
78
+ ::DBus::SessionBus.instance
79
+ true
80
+ rescue ::DBus::Error
81
+ false
82
+ end
83
+
84
+ private
85
+
86
+ def connect_to_session_bus
87
+ @bus = ::DBus::SessionBus.instance
88
+ end
89
+
90
+ def export_interfaces
91
+ @service = @bus.request_service(BUS_NAME)
92
+
93
+ @manager = ManagerInterface.new(OBJECT_PATH)
94
+ @manager.service = self
95
+ @focus_monitor = FocusMonitorInterface.new(OBJECT_PATH)
96
+
97
+ @service.export(@manager)
98
+ @service.export(@focus_monitor)
99
+
100
+ export_status_notifier
101
+
102
+ # Start focus monitoring
103
+ @focus_monitor.start
104
+ end
105
+
106
+ def export_status_notifier
107
+ @status_notifier = StatusNotifierInterface.new("#{OBJECT_PATH}/StatusNotifier")
108
+ @service.export(@status_notifier)
109
+ RosettAi.logger.debug('StatusNotifierItem exported')
110
+ rescue StandardError => e
111
+ RosettAi.logger.warn("StatusNotifierItem unavailable: #{e.message}")
112
+ @status_notifier = nil
113
+ end
114
+
115
+ def request_bus_name
116
+ # Bus name already requested via request_service above
117
+ RosettAi.logger.debug("Registered bus name: #{BUS_NAME}")
118
+ end
119
+ end
120
+ end
121
+ end