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,77 @@
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
+ # Detects the current compositor/window manager.
11
+ #
12
+ # Detection order (first match wins):
13
+ # 1. HYPRLAND_INSTANCE_SIGNATURE env var → :hyprland
14
+ # 2. SWAYSOCK env var → :sway
15
+ # 3. I3SOCK env var → :i3
16
+ # 4. org.gnome.Shell D-Bus name active → :gnome
17
+ # 5. org.kde.KWin D-Bus name active → :kwin
18
+ # 6. DISPLAY set, WAYLAND_DISPLAY unset → :x11
19
+ # 7. else → :unknown
20
+ module CompositorDetector
21
+ COMPOSITOR_CHECKS = [
22
+ { env: 'HYPRLAND_INSTANCE_SIGNATURE', type: :hyprland },
23
+ { env: 'SWAYSOCK', type: :sway },
24
+ { env: 'I3SOCK', type: :i3 }
25
+ ].freeze
26
+
27
+ DBUS_NAMES = {
28
+ 'org.gnome.Shell' => :gnome,
29
+ 'org.kde.KWin' => :kwin
30
+ }.freeze
31
+
32
+ class << self
33
+ # Detect the current compositor
34
+ #
35
+ # @return [Symbol] Compositor type
36
+ def detect
37
+ # Check environment variables first
38
+ COMPOSITOR_CHECKS.each do |check|
39
+ return check[:type] if ENV.key?(check[:env])
40
+ end
41
+
42
+ # Check D-Bus names
43
+ compositor = detect_via_dbus
44
+ return compositor if compositor
45
+
46
+ # Check for X11
47
+ return :x11 if x11_only?
48
+
49
+ :unknown
50
+ end
51
+
52
+ private
53
+
54
+ def detect_via_dbus
55
+ bus = ::DBus::SessionBus.instance
56
+ proxy = bus.service('org.freedesktop.DBus').object('/org/freedesktop/DBus')
57
+ proxy.introspect
58
+ proxy.default_iface = 'org.freedesktop.DBus'
59
+
60
+ names = proxy.ListNames.first
61
+
62
+ DBUS_NAMES.each do |name, type|
63
+ return type if names.include?(name)
64
+ end
65
+
66
+ nil
67
+ rescue ::DBus::Error
68
+ nil
69
+ end
70
+
71
+ def x11_only?
72
+ ENV.key?('DISPLAY') && !ENV.key?('WAYLAND_DISPLAY')
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,59 @@
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
+ module FocusAdapters
9
+ # Base class for compositor-specific focus adapters.
10
+ #
11
+ # Each adapter implements the same interface:
12
+ # - #start: Begin monitoring focus changes
13
+ # - #stop: Stop monitoring
14
+ # - #on_focus_change(&block): Register callback for focus changes
15
+ #
16
+ # Callbacks receive (app_id, title) arguments.
17
+ class Base
18
+ def initialize
19
+ @callbacks = []
20
+ @running = false
21
+ end
22
+
23
+ # Register callback for focus changes
24
+ #
25
+ # @yield [app_id, title] Called on focus change
26
+ def on_focus_change(&block)
27
+ @callbacks << block
28
+ end
29
+
30
+ # Start monitoring focus changes
31
+ def start
32
+ raise NotImplementedError, "#{self.class}#start must be implemented"
33
+ end
34
+
35
+ # Stop monitoring
36
+ def stop
37
+ @running = false
38
+ end
39
+
40
+ # Check if monitoring is active
41
+ #
42
+ # @return [Boolean]
43
+ def running?
44
+ @running
45
+ end
46
+
47
+ protected
48
+
49
+ # Notify all registered callbacks of a focus change
50
+ #
51
+ # @param app_id [String] Application identifier
52
+ # @param title [String] Window title
53
+ def notify_focus_change(app_id, title)
54
+ @callbacks.each { |cb| cb.call(app_id.to_s, title.to_s) }
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,172 @@
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
+ module FocusAdapters
11
+ # Focus adapter for GNOME Shell.
12
+ #
13
+ # Uses GNOME Shell's D-Bus interface to query the focused window.
14
+ # For GNOME < 41, uses org.gnome.Shell.Eval (JavaScript evaluation).
15
+ # For GNOME >= 41, Eval is removed; falls back to xdotool + xprop.
16
+ class GnomeAdapter < Base
17
+ POLL_INTERVAL = 0.5 # seconds
18
+ GNOME_EVAL_REMOVED_VERSION = 41
19
+
20
+ def initialize
21
+ super
22
+ @bus = nil
23
+ @thread = nil
24
+ @last_app_id = nil
25
+ @last_title = nil
26
+ @use_xdotool = false
27
+ end
28
+
29
+ def start
30
+ return if @running
31
+
32
+ @bus = ::DBus::SessionBus.instance
33
+ configure_focus_method
34
+ @running = true
35
+ @thread = Thread.new { run_poll_loop }
36
+ rescue ::DBus::Error => e
37
+ RosettAi.logger.error("Failed to connect to D-Bus: #{e.message}")
38
+ @running = false
39
+ end
40
+
41
+ def stop
42
+ super
43
+ @thread&.kill
44
+ @bus = nil
45
+ end
46
+
47
+ private
48
+
49
+ # Select the focus detection strategy based on GNOME version.
50
+ def configure_focus_method
51
+ version = detect_gnome_version
52
+ @use_xdotool = version.to_i >= GNOME_EVAL_REMOVED_VERSION
53
+ return unless @use_xdotool
54
+
55
+ RosettAi.logger.warn(
56
+ "GNOME #{version} detected; org.gnome.Shell.Eval is unavailable. " \
57
+ 'Falling back to xdotool for focus detection.'
58
+ )
59
+ end
60
+
61
+ def run_poll_loop
62
+ while @running
63
+ poll_focused_window
64
+ sleep POLL_INTERVAL
65
+ end
66
+ rescue StandardError => e
67
+ RosettAi.logger.error("GNOME focus poll error: #{e.message}")
68
+ @running = false
69
+ end
70
+
71
+ def poll_focused_window
72
+ if @use_xdotool
73
+ poll_focused_window_xdotool
74
+ else
75
+ poll_focused_window_eval
76
+ end
77
+ end
78
+
79
+ def poll_focused_window_eval
80
+ result = eval_shell_js('global.display.focus_window ? global.display.focus_window.get_wm_class() : ""')
81
+ app_id = result || ''
82
+
83
+ title_result = eval_shell_js('global.display.focus_window ? global.display.focus_window.get_title() : ""')
84
+ title = title_result || ''
85
+
86
+ return if app_id == @last_app_id && title == @last_title
87
+
88
+ @last_app_id = app_id
89
+ @last_title = title
90
+ notify_focus_change(app_id, title)
91
+ end
92
+
93
+ def poll_focused_window_xdotool
94
+ app_id = poll_active_window_xdotool
95
+ title = app_id ? (poll_active_window_title_xdotool || '') : return
96
+
97
+ return if app_id == @last_app_id && title == @last_title
98
+
99
+ @last_app_id = app_id
100
+ @last_title = title
101
+ notify_focus_change(app_id, title)
102
+ end
103
+
104
+ # Detect GNOME Shell version via the ShellVersion D-Bus property.
105
+ #
106
+ # @return [Integer] Major version number, or 0 if detection fails
107
+ def detect_gnome_version
108
+ shell_object = fetch_shell_object
109
+ shell_object['org.gnome.Shell']['ShellVersion'].to_s.split('.').first.to_i
110
+ rescue ::DBus::Error
111
+ 0
112
+ end
113
+
114
+ # Query the active window WM_CLASS via xdotool and xprop.
115
+ #
116
+ # @return [String, nil] The WM_CLASS instance name, or nil
117
+ def poll_active_window_xdotool
118
+ window_id = IO.popen(['xdotool', 'getactivewindow'], err: File::NULL, &:read).strip
119
+ return if window_id.empty?
120
+
121
+ extract_wm_class(window_id)
122
+ rescue Errno::ENOENT
123
+ handle_missing_xdotool
124
+ end
125
+
126
+ # Extract WM_CLASS from xprop output for a given window ID.
127
+ def extract_wm_class(window_id)
128
+ output = IO.popen(['xprop', '-id', window_id, 'WM_CLASS'], err: File::NULL, &:read)
129
+ match = output.match(/WM_CLASS.*=\s*"([^"]+)"/)
130
+ match ? match[1] : nil
131
+ end
132
+
133
+ # Handle missing xdotool/xprop binary.
134
+ def handle_missing_xdotool
135
+ RosettAi.logger.warn(
136
+ 'xdotool or xprop not found. Install xdotool for focus detection on GNOME 41+: ' \
137
+ 'sudo apt install xdotool'
138
+ )
139
+ @use_xdotool = false
140
+ nil
141
+ end
142
+
143
+ # Query the active window title via xdotool.
144
+ #
145
+ # @return [String, nil] The window title, or nil
146
+ def poll_active_window_title_xdotool
147
+ IO.popen(['xdotool', 'getactivewindow', 'getwindowname'], err: File::NULL, &:read).strip
148
+ rescue Errno::ENOENT
149
+ nil
150
+ end
151
+
152
+ # Fetch the GNOME Shell D-Bus object.
153
+ def fetch_shell_object
154
+ service = @bus.service('org.gnome.Shell')
155
+ object = service.object('/org/gnome/Shell')
156
+ object.introspect
157
+ object
158
+ end
159
+
160
+ def eval_shell_js(code)
161
+ object = fetch_shell_object
162
+ object.default_iface = 'org.gnome.Shell'
163
+
164
+ success, result = object.Eval(code)
165
+ success ? result.gsub(/^"|"$/, '') : nil
166
+ rescue ::DBus::Error
167
+ nil
168
+ end
169
+ end
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,77 @@
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
+ module FocusAdapters
9
+ # Focus adapter for Hyprland compositor.
10
+ #
11
+ # Uses Hyprland's IPC socket2 to receive activewindow events.
12
+ # Socket path: $XDG_RUNTIME_DIR/hypr/$HYPRLAND_INSTANCE_SIGNATURE/.socket2.sock
13
+ class HyprlandAdapter < Base
14
+ def initialize
15
+ super
16
+ @socket = nil
17
+ @thread = nil
18
+ end
19
+
20
+ def start
21
+ return if @running
22
+
23
+ socket_path = hyprland_socket_path
24
+ unless socket_path && File.exist?(socket_path)
25
+ RosettAi.logger.error('Hyprland socket not found')
26
+ return
27
+ end
28
+
29
+ @socket = UNIXSocket.new(socket_path)
30
+ @running = true
31
+ @thread = Thread.new { run_event_loop }
32
+ rescue StandardError => e
33
+ RosettAi.logger.error("Failed to connect to Hyprland: #{e.message}")
34
+ @running = false
35
+ end
36
+
37
+ def stop
38
+ super
39
+ @thread&.kill
40
+ @socket&.close
41
+ @socket = nil
42
+ end
43
+
44
+ private
45
+
46
+ def hyprland_socket_path
47
+ instance_sig = ENV.fetch('HYPRLAND_INSTANCE_SIGNATURE', nil)
48
+ return nil unless instance_sig
49
+
50
+ runtime_dir = ENV.fetch('XDG_RUNTIME_DIR', "/run/user/#{Process.uid}")
51
+ File.join(runtime_dir, 'hypr', instance_sig, '.socket2.sock')
52
+ end
53
+
54
+ def run_event_loop
55
+ while @running
56
+ line = @socket.gets
57
+ break unless line
58
+
59
+ handle_event(line.strip)
60
+ end
61
+ rescue StandardError => e
62
+ RosettAi.logger.error("Hyprland event loop error: #{e.message}")
63
+ @running = false
64
+ end
65
+
66
+ def handle_event(line)
67
+ # Event format: activewindow>>class,title
68
+ return unless line.start_with?('activewindow>>')
69
+
70
+ data = line.sub('activewindow>>', '')
71
+ app_id, title = data.split(',', 2)
72
+ notify_focus_change(app_id || '', title || '')
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,65 @@
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
+ module FocusAdapters
9
+ # Focus adapter for i3 and sway window managers.
10
+ #
11
+ # Uses the i3ipc gem to subscribe to window::focus events
12
+ # via the i3/sway IPC socket.
13
+ class I3Adapter < Base
14
+ def initialize
15
+ super
16
+ @connection = nil
17
+ @thread = nil
18
+ end
19
+
20
+ def start
21
+ return if @running
22
+
23
+ require 'i3ipc'
24
+ @connection = I3Ipc::Connection.new
25
+ @running = true
26
+
27
+ @thread = Thread.new { run_event_loop }
28
+ rescue LoadError
29
+ RosettAi.logger.error('i3ipc gem not available')
30
+ @running = false
31
+ rescue StandardError => e
32
+ RosettAi.logger.error("Failed to connect to i3/sway IPC: #{e.message}")
33
+ @running = false
34
+ end
35
+
36
+ def stop
37
+ super
38
+ @thread&.kill
39
+ @connection = nil
40
+ end
41
+
42
+ private
43
+
44
+ def run_event_loop
45
+ @connection.subscribe('window') do |event|
46
+ handle_window_event(event) if event['change'] == 'focus'
47
+ end
48
+ rescue StandardError => e
49
+ RosettAi.logger.error("i3/sway event loop error: #{e.message}")
50
+ @running = false
51
+ end
52
+
53
+ def handle_window_event(event)
54
+ container = event['container']
55
+ return unless container
56
+
57
+ app_id = container['app_id'] || container['window_properties']&.fetch('class', '') || ''
58
+ title = container['name'] || ''
59
+
60
+ notify_focus_change(app_id, title)
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -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
+ module FocusAdapters
11
+ # Focus adapter for KDE KWin.
12
+ #
13
+ # Uses KWin's D-Bus scripting API to monitor focus changes.
14
+ # Listens for the activeWindowChanged signal from org.kde.KWin.
15
+ class KwinAdapter < Base
16
+ def initialize
17
+ super
18
+ @bus = nil
19
+ @main_loop = nil
20
+ @thread = nil
21
+ end
22
+
23
+ def start
24
+ return if @running
25
+
26
+ @bus = ::DBus::SessionBus.instance
27
+ @running = true
28
+ setup_signal_handler
29
+ @thread = Thread.new { run_event_loop }
30
+ rescue ::DBus::Error => e
31
+ RosettAi.logger.error("Failed to connect to KWin D-Bus: #{e.message}")
32
+ @running = false
33
+ end
34
+
35
+ def stop
36
+ super
37
+ @main_loop&.quit
38
+ @thread&.kill
39
+ @bus = nil
40
+ end
41
+
42
+ private
43
+
44
+ def setup_signal_handler
45
+ service = @bus.service('org.kde.KWin')
46
+ object = service.object('/KWin')
47
+ object.introspect
48
+
49
+ object.on_signal('activeWindowChanged') do
50
+ poll_active_window
51
+ end
52
+ rescue ::DBus::Error
53
+ RosettAi.logger.warn('KWin activeWindowChanged signal not available, falling back to polling')
54
+ @polling_mode = true
55
+ end
56
+
57
+ def run_event_loop
58
+ if @polling_mode
59
+ run_poll_loop
60
+ else
61
+ @main_loop = ::DBus::Main.new
62
+ @main_loop << @bus
63
+ @main_loop.run
64
+ end
65
+ rescue StandardError => e
66
+ RosettAi.logger.error("KWin event loop error: #{e.message}")
67
+ @running = false
68
+ end
69
+
70
+ def run_poll_loop
71
+ while @running
72
+ poll_active_window
73
+ sleep 0.5
74
+ end
75
+ end
76
+
77
+ def poll_active_window
78
+ service = @bus.service('org.kde.KWin')
79
+ object = service.object('/KWin')
80
+ object.introspect
81
+ object.default_iface = 'org.kde.KWin'
82
+
83
+ # KWin API varies by version - try to get active window info
84
+ # For now, we use a basic polling approach that may need adaptation
85
+ app_id = ''
86
+ title = ''
87
+
88
+ begin
89
+ # Try KDE 6 approach first
90
+ object.default_iface = 'org.kde.KWin.Scripting'
91
+ # KWin scripting is complex; for now just poll basic info
92
+ rescue ::DBus::Error
93
+ # Fall through to notify
94
+ end
95
+
96
+ notify_focus_change(app_id, title)
97
+ rescue ::DBus::Error
98
+ # KWin API varies, ignore errors
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,105 @@
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
+ module FocusAdapters
9
+ # Focus adapter for X11 window managers.
10
+ #
11
+ # Uses xprop to monitor _NET_ACTIVE_WINDOW property changes.
12
+ # Falls back to polling if xprop spy mode is not available.
13
+ class X11Adapter < Base
14
+ POLL_INTERVAL = 0.5 # seconds
15
+
16
+ def initialize
17
+ super
18
+ @thread = nil
19
+ @xprop_process = nil
20
+ @last_window_id = nil
21
+ end
22
+
23
+ def start
24
+ return if @running
25
+ return unless xprop_available?
26
+
27
+ @running = true
28
+ @thread = Thread.new { run_poll_loop }
29
+ end
30
+
31
+ def stop
32
+ super
33
+ @thread&.kill
34
+ @xprop_process&.close
35
+ end
36
+
37
+ private
38
+
39
+ def xprop_available?
40
+ system('which xprop > /dev/null 2>&1')
41
+ end
42
+
43
+ def run_poll_loop
44
+ while @running
45
+ poll_active_window
46
+ sleep POLL_INTERVAL
47
+ end
48
+ rescue StandardError => e
49
+ RosettAi.logger.error("X11 focus poll error: #{e.message}")
50
+ @running = false
51
+ end
52
+
53
+ def poll_active_window
54
+ win_id = active_window_id
55
+ return if win_id == @last_window_id
56
+ return unless win_id
57
+
58
+ @last_window_id = win_id
59
+ app_id, title = window_info(win_id)
60
+ notify_focus_change(app_id, title)
61
+ end
62
+
63
+ def active_window_id
64
+ output = IO.popen(['xprop', '-root', '_NET_ACTIVE_WINDOW'], err: File::NULL, &:read)
65
+ match = output.match(/window id # (0x[0-9a-f]+)/i)
66
+ match ? match[1] : nil
67
+ rescue Errno::ENOENT
68
+ nil
69
+ end
70
+
71
+ def window_info(window_id)
72
+ return ['', ''] unless window_id
73
+
74
+ app_id = extract_wm_class(window_id)
75
+ title = extract_window_title(window_id)
76
+
77
+ [app_id, title]
78
+ end
79
+
80
+ def extract_wm_class(window_id)
81
+ output = IO.popen(['xprop', '-id', window_id, 'WM_CLASS'], err: File::NULL, &:read)
82
+ match = output.match(/WM_CLASS\(STRING\) = "([^"]*)", "([^"]*)"/)
83
+ match ? match[2] : ''
84
+ rescue Errno::ENOENT
85
+ ''
86
+ end
87
+
88
+ def extract_window_title(window_id)
89
+ # Try _NET_WM_NAME first
90
+ output = IO.popen(['xprop', '-id', window_id, '_NET_WM_NAME'], err: File::NULL, &:read)
91
+ if (match = output.match(/_NET_WM_NAME\(UTF8_STRING\) = "(.*)"/))
92
+ return match[1]
93
+ end
94
+
95
+ # Fallback to WM_NAME
96
+ output = IO.popen(['xprop', '-id', window_id, 'WM_NAME'], err: File::NULL, &:read)
97
+ match = output.match(/WM_NAME\(.*\) = "(.*)"/)
98
+ match ? match[1] : ''
99
+ rescue Errno::ENOENT
100
+ ''
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end