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,236 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-License-Identifier: GPL-3.0-only
4
+ # Copyright (C) 2026 Hugo Antonio Sepulveda Manriquez / NeatNerds
5
+
6
+ require 'json'
7
+ require 'time'
8
+
9
+ module RosettAi
10
+ module Desktop
11
+ # Structured GUI action logger for debugging and crash analysis.
12
+ #
13
+ # Provides comprehensive logging of all GUI interactions including:
14
+ # - Action invocation with parameters
15
+ # - Success/failure outcomes with duration
16
+ # - Widget events and state changes
17
+ # - D-Bus operations
18
+ #
19
+ # Log output is controlled by environment variables:
20
+ # RAI_GUI_LOG=1 Enable logging to stderr
21
+ # RAI_GUI_LOG_FILE=path Also log to file
22
+ # RAI_GUI_LOG_LEVEL=debug Set log level (debug, info, warn, error)
23
+ #
24
+ # Log format is JSON Lines for easy parsing and analysis.
25
+ #
26
+ # @example Enable logging
27
+ # RAI_GUI_LOG=1 bin/raictl desktop gtk4
28
+ #
29
+ # @example Log to file
30
+ # RAI_GUI_LOG=1 RAI_GUI_LOG_FILE=/tmp/rosett-ai-gui.log bin/raictl desktop gtk4
31
+ #
32
+ class GuiLogger
33
+ LEVELS = { debug: 0, info: 1, warn: 2, error: 3 }.freeze
34
+ DEFAULT_LEVEL = :info
35
+
36
+ class << self
37
+ # Get the singleton instance.
38
+ #
39
+ # @return [GuiLogger]
40
+ def instance
41
+ @instance ||= new
42
+ end
43
+
44
+ # Delegate class methods to instance for convenience.
45
+ [:action, :action_start, :action_end, :callback, :widget_event, :dbus_call, :debug, :info, :warn, :error,
46
+ :enabled?].each do |method|
47
+ define_method(method) { |*args, **kwargs, &block| instance.send(method, *args, **kwargs, &block) }
48
+ end
49
+ end
50
+
51
+ def initialize
52
+ @enabled = ENV['RAI_GUI_LOG'] == '1'
53
+ @log_file = ENV.fetch('RAI_GUI_LOG_FILE', nil)
54
+ @level = parse_level(ENV.fetch('RAI_GUI_LOG_LEVEL', 'info'))
55
+ @file_handle = nil
56
+ @action_stack = []
57
+ @mutex = Mutex.new
58
+
59
+ setup_file_logging if @log_file
60
+ at_exit { cleanup }
61
+ end
62
+
63
+ # Check if logging is enabled.
64
+ #
65
+ # @return [Boolean]
66
+ def enabled?
67
+ @enabled
68
+ end
69
+
70
+ # Log a complete action with automatic timing.
71
+ #
72
+ # @param name [String, Symbol] action name
73
+ # @param context [Hash] additional context
74
+ # @yield the action block
75
+ # @return [Object] result of the block
76
+ def action(name, context = {})
77
+ return yield unless @enabled
78
+
79
+ action_start(name, context)
80
+ start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
81
+
82
+ begin
83
+ result = yield
84
+ duration = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time) * 1000).round(2)
85
+ action_end(name, status: :success, duration_ms: duration)
86
+ result
87
+ rescue StandardError => e
88
+ duration = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time) * 1000).round(2)
89
+ action_end(name, status: :error, duration_ms: duration, error: e.class.name, message: e.message)
90
+ raise
91
+ end
92
+ end
93
+
94
+ # Log action start.
95
+ #
96
+ # @param name [String, Symbol] action name
97
+ # @param context [Hash] additional context
98
+ def action_start(name, context = {})
99
+ return unless @enabled
100
+
101
+ @mutex.synchronize { @action_stack.push(name.to_s) }
102
+ write_log(:info, 'action_start', { action: name.to_s }.merge(context))
103
+ end
104
+
105
+ # Log action end.
106
+ #
107
+ # @param name [String, Symbol] action name
108
+ # @param context [Hash] additional context (status, duration_ms, error)
109
+ def action_end(name, context = {})
110
+ return unless @enabled
111
+
112
+ @mutex.synchronize { @action_stack.pop }
113
+ level = context[:status] == :error ? :error : :info
114
+ write_log(level, 'action_end', { action: name.to_s }.merge(context))
115
+ end
116
+
117
+ # Log a callback invocation.
118
+ #
119
+ # @param name [String, Symbol] callback name
120
+ # @param context [Hash] additional context
121
+ def callback(name, context = {})
122
+ return unless @enabled
123
+
124
+ write_log(:debug, 'callback', { callback: name.to_s }.merge(context))
125
+ end
126
+
127
+ # Log a widget event.
128
+ #
129
+ # @param widget_type [String, Symbol] widget type (e.g., :switch_row, :combo_row)
130
+ # @param event [String, Symbol] event name (e.g., :toggled, :selected)
131
+ # @param context [Hash] additional context (value, widget_id, etc.)
132
+ def widget_event(widget_type, event, context = {})
133
+ return unless @enabled
134
+
135
+ write_log(:debug, 'widget_event', {
136
+ widget: widget_type.to_s,
137
+ event: event.to_s
138
+ }.merge(context))
139
+ end
140
+
141
+ # Log a D-Bus operation.
142
+ #
143
+ # @param operation [String, Symbol] operation name
144
+ # @param context [Hash] additional context
145
+ def dbus_call(operation, context = {})
146
+ return unless @enabled
147
+
148
+ write_log(:debug, 'dbus_call', { operation: operation.to_s }.merge(context))
149
+ end
150
+
151
+ # Log debug message.
152
+ #
153
+ # @param message [String] log message
154
+ # @param context [Hash] additional context
155
+ def debug(message, context = {})
156
+ write_log(:debug, 'debug', { message: message }.merge(context))
157
+ end
158
+
159
+ # Log info message.
160
+ #
161
+ # @param message [String] log message
162
+ # @param context [Hash] additional context
163
+ def info(message, context = {})
164
+ write_log(:info, 'info', { message: message }.merge(context))
165
+ end
166
+
167
+ # Log warning message.
168
+ #
169
+ # @param message [String] log message
170
+ # @param context [Hash] additional context
171
+ def warn(message, context = {})
172
+ write_log(:warn, 'warn', { message: message }.merge(context))
173
+ end
174
+
175
+ # Log error message.
176
+ #
177
+ # @param message [String] log message
178
+ # @param error [Exception, nil] optional exception
179
+ # @param context [Hash] additional context
180
+ def error(message, error = nil, context = {})
181
+ data = { message: message }.merge(context)
182
+ if error
183
+ data[:error_class] = error.class.name
184
+ data[:error_message] = error.message
185
+ data[:backtrace] = error.backtrace&.first(5)
186
+ end
187
+ write_log(:error, 'error', data)
188
+ end
189
+
190
+ private
191
+
192
+ def parse_level(level_str)
193
+ LEVELS.fetch(level_str.to_sym, LEVELS[DEFAULT_LEVEL])
194
+ end
195
+
196
+ def setup_file_logging
197
+ @file_handle = File.open(@log_file, 'a') # rubocop:disable Style/FileOpen -- persistent handle, closed in #cleanup
198
+ @file_handle.sync = true
199
+ rescue StandardError => e
200
+ warn "[GuiLogger] Failed to open log file: #{e.message}"
201
+ @file_handle = nil
202
+ end
203
+
204
+ def cleanup
205
+ @file_handle&.close
206
+ rescue StandardError
207
+ nil
208
+ end
209
+
210
+ def write_log(level, event_type, data)
211
+ return unless @enabled
212
+ return if LEVELS.fetch(level, 0) < @level
213
+
214
+ entry = {
215
+ timestamp: Time.now.utc.iso8601(3),
216
+ level: level.to_s,
217
+ event: event_type,
218
+ pid: Process.pid
219
+ }.merge(data)
220
+
221
+ # Add action context if within an action
222
+ entry[:action_stack] = @action_stack.dup unless @action_stack.empty?
223
+
224
+ json_line = JSON.generate(entry)
225
+
226
+ @mutex.synchronize do
227
+ warn json_line
228
+ @file_handle&.puts(json_line)
229
+ end
230
+ rescue StandardError
231
+ # Never let logging cause issues
232
+ nil
233
+ end
234
+ end
235
+ end
236
+ end
@@ -0,0 +1,92 @@
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 Doctor
8
+ # Base interface for diagnostic checks.
9
+ #
10
+ # Include this module and implement #perform to create a check.
11
+ # The #run method calls #perform and sets status/message/remediation.
12
+ #
13
+ # @author hugo
14
+ # @author claude
15
+ module Check
16
+ # @return [String] human-readable check name
17
+ def name
18
+ self.class.check_name
19
+ end
20
+
21
+ # @return [Symbol] :pass, :warn, or :fail
22
+ attr_reader :status
23
+
24
+ # @return [String] result description
25
+ attr_reader :message
26
+
27
+ # @return [String, nil] remediation steps (nil when passing)
28
+ attr_reader :remediation
29
+
30
+ # Execute the check. Sets status, message, and remediation.
31
+ # @return [void]
32
+ def run
33
+ perform
34
+ rescue StandardError => e
35
+ fail!(e.message, remediation: ::I18n.t('rosett_ai.doctor.unexpected_error'))
36
+ end
37
+
38
+ # Register this check class with the Doctor module.
39
+ # @param check_name [String] unique check identifier
40
+ def self.included(base)
41
+ base.extend(ClassMethods)
42
+ end
43
+
44
+ # Class-level methods for check classes.
45
+ module ClassMethods
46
+ # Set or get the check name.
47
+ # @param name [String, nil] check name to set
48
+ # @return [String]
49
+ def check_name(name = nil)
50
+ @check_name = name if name
51
+ @check_name || (self.name || 'anonymous').split('::').last.gsub(/Check$/, '').gsub(/([A-Z])/, '_\1').sub(
52
+ /^_/, ''
53
+ ).downcase
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ # Mark the check as passing.
60
+ # @param message [String]
61
+ def pass!(message)
62
+ @status = :pass
63
+ @message = message
64
+ @remediation = nil
65
+ end
66
+
67
+ # Mark the check as warning.
68
+ # @param message [String]
69
+ # @param remediation [String]
70
+ def warn!(message, remediation:)
71
+ @status = :warn
72
+ @message = message
73
+ @remediation = remediation
74
+ end
75
+
76
+ # Mark the check as failed.
77
+ # @param message [String]
78
+ # @param remediation [String]
79
+ def fail!(message, remediation:)
80
+ @status = :fail
81
+ @message = message
82
+ @remediation = remediation
83
+ end
84
+
85
+ # Subclasses must implement this method.
86
+ # @abstract
87
+ def perform
88
+ raise NotImplementedError, "#{self.class}#perform must be implemented"
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-License-Identifier: GPL-3.0-only
4
+ # Copyright (C) 2026 Hugo Antonio Sepulveda Manriquez / NeatNerds
5
+
6
+ module RosettAi
7
+ module Doctor
8
+ module Checks
9
+ # Verifies the cache directory exists and is writable.
10
+ #
11
+ # @author hugo
12
+ # @author claude
13
+ class CacheHealthCheck
14
+ include Check
15
+
16
+ check_name 'cache_health'
17
+
18
+ private
19
+
20
+ def perform
21
+ cache_dir = resolve_cache_dir
22
+
23
+ unless cache_dir.exist?
24
+ warn!(
25
+ ::I18n.t('rosett_ai.doctor.cache_health.not_found', path: cache_dir),
26
+ remediation: ::I18n.t('rosett_ai.doctor.cache_health.create_dir', path: cache_dir)
27
+ )
28
+ return
29
+ end
30
+
31
+ if cache_dir.writable?
32
+ pass!(::I18n.t('rosett_ai.doctor.cache_health.writable', path: cache_dir))
33
+ else
34
+ fail!(
35
+ ::I18n.t('rosett_ai.doctor.cache_health.not_writable', path: cache_dir),
36
+ remediation: ::I18n.t('rosett_ai.doctor.cache_health.fix_permissions', path: cache_dir)
37
+ )
38
+ end
39
+ end
40
+
41
+ def resolve_cache_dir
42
+ xdg = ENV.fetch('XDG_CACHE_HOME', File.join(Dir.home, '.cache'))
43
+ Pathname.new(xdg).join('rosett-ai')
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ RosettAi::Doctor.register(RosettAi::Doctor::Checks::CacheHealthCheck)
@@ -0,0 +1,39 @@
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 Doctor
8
+ module Checks
9
+ # Checks whether the D-Bus session bus is available.
10
+ #
11
+ # D-Bus is optional — missing D-Bus is a warning, not a failure.
12
+ #
13
+ # @author hugo
14
+ # @author claude
15
+ class DbusAvailabilityCheck
16
+ include Check
17
+
18
+ check_name 'dbus_availability'
19
+
20
+ private
21
+
22
+ def perform
23
+ bus_address = ENV.fetch('DBUS_SESSION_BUS_ADDRESS', nil)
24
+
25
+ if bus_address && !bus_address.empty?
26
+ pass!(::I18n.t('rosett_ai.doctor.dbus_availability.available'))
27
+ else
28
+ warn!(
29
+ ::I18n.t('rosett_ai.doctor.dbus_availability.unavailable'),
30
+ remediation: ::I18n.t('rosett_ai.doctor.dbus_availability.install_dbus')
31
+ )
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ RosettAi::Doctor.register(RosettAi::Doctor::Checks::DbusAvailabilityCheck)
@@ -0,0 +1,46 @@
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 Doctor
8
+ module Checks
9
+ # Detects installed rai engines.
10
+ #
11
+ # No engines is a warning (rosett-ai still works for validation/compilation
12
+ # with the generic target).
13
+ #
14
+ # @author hugo
15
+ # @author claude
16
+ class EngineDetectionCheck
17
+ include Check
18
+
19
+ check_name 'engine_detection'
20
+
21
+ private
22
+
23
+ def perform
24
+ engines = discover_engines
25
+
26
+ if engines.empty?
27
+ warn!(
28
+ ::I18n.t('rosett_ai.doctor.engine_detection.none_found'),
29
+ remediation: ::I18n.t('rosett_ai.doctor.engine_detection.install_engine')
30
+ )
31
+ else
32
+ pass!(::I18n.t('rosett_ai.doctor.engine_detection.found', engines: engines.join(', ')))
33
+ end
34
+ end
35
+
36
+ def discover_engines
37
+ RosettAi::Plugins::Registry.available(:engine)
38
+ rescue StandardError
39
+ []
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ RosettAi::Doctor.register(RosettAi::Doctor::Checks::EngineDetectionCheck)
@@ -0,0 +1,44 @@
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 Doctor
8
+ module Checks
9
+ # Verifies key directories are writable.
10
+ #
11
+ # @author hugo
12
+ # @author claude
13
+ class FilePermissionCheck
14
+ include Check
15
+
16
+ check_name 'file_permissions'
17
+
18
+ private
19
+
20
+ def perform
21
+ resolver = RosettAi::PathResolver.new
22
+ dirs = [resolver.global_dir, resolver.rai_config_dir]
23
+ problems = dirs.each_with_object([]) do |dir, acc|
24
+ next unless dir.exist?
25
+ next if dir.writable?
26
+
27
+ acc << dir.to_s
28
+ end
29
+
30
+ if problems.empty?
31
+ pass!(::I18n.t('rosett_ai.doctor.file_permissions.writable'))
32
+ else
33
+ fail!(
34
+ ::I18n.t('rosett_ai.doctor.file_permissions.not_writable', dirs: problems.join(', ')),
35
+ remediation: ::I18n.t('rosett_ai.doctor.file_permissions.fix_permissions')
36
+ )
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ RosettAi::Doctor.register(RosettAi::Doctor::Checks::FilePermissionCheck)
@@ -0,0 +1,55 @@
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 Doctor
8
+ module Checks
9
+ # Verifies all Gemfile.lock dependencies are installed.
10
+ #
11
+ # @author hugo
12
+ # @author claude
13
+ class GemDependencyCheck
14
+ include Check
15
+
16
+ check_name 'gem_dependencies'
17
+
18
+ private
19
+
20
+ def perform
21
+ lockfile = RosettAi.root.join('Gemfile.lock')
22
+
23
+ unless lockfile.exist?
24
+ warn!(
25
+ ::I18n.t('rosett_ai.doctor.gem_dependencies.no_lockfile'),
26
+ remediation: ::I18n.t('rosett_ai.doctor.gem_dependencies.run_bundle')
27
+ )
28
+ return
29
+ end
30
+
31
+ missing = find_missing_gems
32
+ if missing.empty?
33
+ pass!(::I18n.t('rosett_ai.doctor.gem_dependencies.all_installed',
34
+ count: Gem.loaded_specs.size))
35
+ else
36
+ fail!(
37
+ ::I18n.t('rosett_ai.doctor.gem_dependencies.missing', gems: missing.join(', ')),
38
+ remediation: ::I18n.t('rosett_ai.doctor.gem_dependencies.run_bundle')
39
+ )
40
+ end
41
+ end
42
+
43
+ def find_missing_gems
44
+ Bundler.locked_gems.specs.each_with_object([]) do |spec, missing|
45
+ missing << spec.name unless Gem.loaded_specs.key?(spec.name)
46
+ end
47
+ rescue StandardError
48
+ []
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ RosettAi::Doctor.register(RosettAi::Doctor::Checks::GemDependencyCheck)
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-License-Identifier: GPL-3.0-only
4
+ # Copyright (C) 2026 Hugo Antonio Sepulveda Manriquez / NeatNerds
5
+
6
+ module RosettAi
7
+ module Doctor
8
+ module Checks
9
+ # Verifies the running Ruby version matches .ruby-version.
10
+ #
11
+ # @author hugo
12
+ # @author claude
13
+ class RubyVersionCheck
14
+ include Check
15
+
16
+ check_name 'ruby_version'
17
+
18
+ private
19
+
20
+ def perform
21
+ expected = read_ruby_version
22
+ actual = RUBY_VERSION
23
+
24
+ if expected.nil?
25
+ warn!(
26
+ ::I18n.t('rosett_ai.doctor.ruby_version.no_file'),
27
+ remediation: ::I18n.t('rosett_ai.doctor.ruby_version.create_file')
28
+ )
29
+ elsif actual == expected
30
+ pass!(::I18n.t('rosett_ai.doctor.ruby_version.match', version: actual))
31
+ else
32
+ fail!(
33
+ ::I18n.t('rosett_ai.doctor.ruby_version.mismatch', actual: actual, expected: expected),
34
+ remediation: ::I18n.t('rosett_ai.doctor.ruby_version.install', version: expected)
35
+ )
36
+ end
37
+ end
38
+
39
+ def read_ruby_version
40
+ path = RosettAi.root.join('.ruby-version')
41
+ return nil unless path.exist?
42
+
43
+ path.read.strip
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ RosettAi::Doctor.register(RosettAi::Doctor::Checks::RubyVersionCheck)
@@ -0,0 +1,57 @@
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 Doctor
8
+ module Checks
9
+ # Warns when a stale ~/.config/nncc/ directory exists.
10
+ #
11
+ # The legacy XDG config path was ~/.config/nncc/ before the rename to
12
+ # rosett-ai. This check detects the stale directory and recommends
13
+ # running `raictl migrate` to move the config.
14
+ #
15
+ # @author hugo
16
+ # @author claude
17
+ class StaleConfigNnccCheck
18
+ include Check
19
+
20
+ check_name 'stale_config_nncc'
21
+
22
+ private
23
+
24
+ def perform
25
+ config_nncc = Pathname.new(Dir.home).join('.config', 'nncc')
26
+
27
+ unless config_nncc.directory?
28
+ pass!(::I18n.t('rosett_ai.doctor.stale_config_nncc.clean'))
29
+ return
30
+ end
31
+
32
+ warn_stale(config_nncc)
33
+ end
34
+
35
+ def warn_stale(config_nncc)
36
+ path_str = config_nncc.to_s
37
+ remediation = ::I18n.t('rosett_ai.doctor.stale_config_nncc.migrate')
38
+ children = config_nncc.children
39
+
40
+ if children.any?
41
+ warn!(
42
+ ::I18n.t('rosett_ai.doctor.stale_config_nncc.found', path: path_str, count: children.count),
43
+ remediation: remediation
44
+ )
45
+ else
46
+ warn!(
47
+ ::I18n.t('rosett_ai.doctor.stale_config_nncc.empty', path: path_str),
48
+ remediation: remediation
49
+ )
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ RosettAi::Doctor.register(RosettAi::Doctor::Checks::StaleConfigNnccCheck)