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,74 @@
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 Workflow
8
+ module Steps
9
+ # Executes an internal rai CLI subcommand.
10
+ #
11
+ # Resolves the rai binary via PATH lookup, falling back to the
12
+ # development tree's bin/raictl.
13
+ #
14
+ # @author hugo
15
+ # @author claude
16
+ class RaiStep
17
+ # @param definition [Hash] step definition from workflow YAML
18
+ def initialize(definition)
19
+ @definition = definition
20
+ @command = definition.fetch('command')
21
+ validate!
22
+ end
23
+
24
+ # @return [String] step description for dry-run
25
+ def describe
26
+ "[raictl] #{@definition['name']} — #{@command}"
27
+ end
28
+
29
+ # Executes the rai subcommand.
30
+ #
31
+ # @return [Hash] execution result with :status and :message
32
+ def execute
33
+ argv = @command.split
34
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
35
+ success = system(resolve_rai_binary, *argv)
36
+ elapsed = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000).round(1)
37
+
38
+ if success
39
+ { status: 'pass', message: "Completed: raictl #{@command}", duration_ms: elapsed }
40
+ else
41
+ { status: 'fail', message: "Failed: raictl #{@command} (exit #{$CHILD_STATUS&.exitstatus})",
42
+ duration_ms: elapsed }
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ # Resolves the rai binary path.
49
+ #
50
+ # Prefers a system-installed binary found via PATH, falls back
51
+ # to the development tree's bin/raictl.
52
+ #
53
+ # @return [String] absolute path to the rai binary
54
+ def resolve_rai_binary
55
+ @resolve_rai_binary ||= begin
56
+ which_result = `which rai 2>/dev/null`.strip
57
+ if $CHILD_STATUS&.success? && !which_result.empty?
58
+ which_result
59
+ else
60
+ RosettAi.root.join('bin', 'rai').to_s
61
+ end
62
+ end
63
+ end
64
+
65
+ def validate!
66
+ return if @command.is_a?(String)
67
+
68
+ raise RosettAi::WorkflowError,
69
+ "Rai step '#{@definition['name']}' command must be a string"
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,53 @@
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 Workflow
8
+ module Steps
9
+ # Executes a shell command in array-form (no string interpolation).
10
+ #
11
+ # @author hugo
12
+ # @author claude
13
+ class ShellStep
14
+ # @param definition [Hash] step definition from workflow YAML
15
+ def initialize(definition)
16
+ @definition = definition
17
+ @command = definition.fetch('command')
18
+ validate!
19
+ end
20
+
21
+ # @return [String] step description for dry-run
22
+ def describe
23
+ "[shell] #{@definition['name']} — #{@command.join(' ')}"
24
+ end
25
+
26
+ # Executes the shell command.
27
+ #
28
+ # @return [Hash] execution result with :status and :message
29
+ def execute
30
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
31
+ success = system(*@command)
32
+ elapsed = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000).round(1)
33
+
34
+ if success
35
+ { status: 'pass', message: "Completed: #{@command.join(' ')}", duration_ms: elapsed }
36
+ else
37
+ { status: 'fail', message: "Failed: #{@command.join(' ')} (exit #{$CHILD_STATUS&.exitstatus})",
38
+ duration_ms: elapsed }
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def validate!
45
+ return if @command.is_a?(Array)
46
+
47
+ raise RosettAi::WorkflowError,
48
+ "Shell step '#{@definition['name']}' command must be an array"
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,78 @@
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 'yaml'
7
+
8
+ module RosettAi
9
+ # Centralized YAML loading with bounds checking.
10
+ #
11
+ # Enforces file-size, nesting-depth, and key-count limits to prevent
12
+ # denial-of-service via maliciously crafted YAML payloads.
13
+ module YamlLoader
14
+ MAX_FILE_SIZE = 1_048_576 # 1 MB
15
+ MAX_DEPTH = 10
16
+ MAX_KEY_COUNT = 1000
17
+
18
+ # Loads and validates a YAML file with bounds checking.
19
+ #
20
+ # Enforces file size, nesting depth, and key count limits before
21
+ # returning the parsed data.
22
+ #
23
+ # @param path [String] absolute path to the YAML file
24
+ # @param permitted_classes [Array<Class>] classes allowed during safe loading
25
+ # @return [Hash] parsed YAML data
26
+ # @raise [RosettAi::ValidationError] if file size, nesting depth, or key count
27
+ # exceeds the configured limits
28
+ def self.load_file(path, permitted_classes: [Date, Time])
29
+ check_file_size!(path)
30
+ data = YAML.safe_load_file(path, permitted_classes: permitted_classes)
31
+ check_depth!(data, path)
32
+ check_key_count!(data, path)
33
+ data
34
+ end
35
+
36
+ def self.check_file_size!(path)
37
+ size = File.size(path)
38
+ return if size <= MAX_FILE_SIZE
39
+
40
+ raise RosettAi::ValidationError,
41
+ "YAML file exceeds #{MAX_FILE_SIZE} byte limit (#{size} bytes): #{path}"
42
+ end
43
+ private_class_method :check_file_size!
44
+
45
+ def self.check_depth!(data, path, current = 0)
46
+ raise RosettAi::ValidationError, "YAML nesting exceeds #{MAX_DEPTH} levels: #{path}" if current > MAX_DEPTH
47
+
48
+ case data
49
+ when Hash
50
+ data.each_value { |v| check_depth!(v, path, current + 1) }
51
+ when Array
52
+ data.each { |v| check_depth!(v, path, current + 1) }
53
+ end
54
+ end
55
+ private_class_method :check_depth!
56
+
57
+ def self.check_key_count!(data, path)
58
+ count = count_keys(data)
59
+ return if count <= MAX_KEY_COUNT
60
+
61
+ raise RosettAi::ValidationError,
62
+ "YAML key count exceeds #{MAX_KEY_COUNT} (#{count} keys): #{path}"
63
+ end
64
+ private_class_method :check_key_count!
65
+
66
+ def self.count_keys(data)
67
+ case data
68
+ when Hash
69
+ data.size + data.each_value.sum { |v| count_keys(v) }
70
+ when Array
71
+ data.sum { |v| count_keys(v) }
72
+ else
73
+ 0
74
+ end
75
+ end
76
+ private_class_method :count_keys
77
+ end
78
+ end
data/lib/rosett_ai.rb ADDED
@@ -0,0 +1,221 @@
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 'zeitwerk'
7
+ require 'json'
8
+ require 'logger'
9
+ require 'pathname'
10
+ require 'i18n'
11
+
12
+ # NeatNerds Code Companion — engine-agnostic configuration management
13
+ # for AI-assisted development workflows.
14
+ #
15
+ # Author rules once in YAML, compile to any AI tool: Claude, Cursor, AGENTS.md,
16
+ # and any engine with a pluggable backend.
17
+ #
18
+ # @see https://neatnerds.be/rosett-ai
19
+ module RosettAi
20
+ # @!group Error hierarchy
21
+
22
+ # Base error for all rosett-ai operations.
23
+ class Error < StandardError; end
24
+ # Raised when configuration files are invalid or unreadable.
25
+ class ConfigurationError < Error; end
26
+ # Raised when schema or bounds validation fails.
27
+ class ValidationError < Error; end
28
+ # Raised when behaviour file processing fails.
29
+ class BehaviourError < Error; end
30
+ # Raised when compilation encounters an unrecoverable error.
31
+ class CompileError < Error; end
32
+ # Raised when rule adoption analysis fails.
33
+ class AdoptError < Error; end
34
+ # Raised when workspace initialization fails.
35
+ class InitError < Error; end
36
+ # Raised when backup creation or restoration fails.
37
+ class BackupError < Error; end
38
+ # Raised when package build fails.
39
+ class BuildError < Error; end
40
+ # Raised when design document operations fail.
41
+ class DesignError < Error; end
42
+ # Raised when documentation generation fails.
43
+ class DocumentationError < Error; end
44
+ # Raised when release management operations fail.
45
+ class ReleaseError < Error; end
46
+ # Raised when tooling validation fails.
47
+ class ToolingError < Error; end
48
+ # Raised when license activation or validation fails.
49
+ class LicenseError < Error; end
50
+ # Raised when content pack operations fail.
51
+ class ContentError < Error; end
52
+ # Raised when behaviour composition fails.
53
+ class CompositionError < Error; end
54
+ # Raised when AI provenance operations fail.
55
+ class ProvenanceError < Error; end
56
+ # Raised when AI tool configuration operations fail.
57
+ class AiConfigError < Error; end
58
+ # Raised when AI authorship attribution fails.
59
+ class AuthorshipError < Error; end
60
+ # Raised when policy management operations fail.
61
+ class PolicyError < Error; end
62
+ # Raised when doctor diagnostics encounter an unexpected error.
63
+ class DoctorError < Error; end
64
+ # Raised when an experimental feature is invoked without its flag enabled.
65
+ class FeatureFlagError < Error; end
66
+ # Raised when compliance checks encounter an unrecoverable error.
67
+ class ComplianceError < Error; end
68
+ # Raised when workflow definition or execution fails.
69
+ class WorkflowError < Error; end
70
+ # Raised when project management operations fail.
71
+ class ProjectError < Error; end
72
+ # Raised when reverse compilation (retrofit) fails.
73
+ class RetrofitError < Error; end
74
+ # Raised when git hook operations fail.
75
+ class GitHooksError < Error; end
76
+ # Raised when MCP server or admin operations fail.
77
+ class McpError < Error; end
78
+
79
+ # @!endgroup
80
+
81
+ class << self
82
+ # Project root directory (where the gemspec lives).
83
+ #
84
+ # @return [Pathname]
85
+ def root
86
+ @root ||= Pathname.new(File.expand_path('..', __dir__))
87
+ end
88
+
89
+ # Zeitwerk autoloader instance. Sets up inflections, ignores
90
+ # deprecated stubs and optional gems, then calls +setup+.
91
+ #
92
+ # @return [Zeitwerk::Loader]
93
+ def loader
94
+ @loader ||= begin
95
+ loader = Zeitwerk::Loader.for_gem
96
+ loader.inflector.inflect('cli' => 'CLI', 'dbus' => 'DBus')
97
+ loader.ignore("#{__dir__}/rubocop")
98
+ loader.ignore("#{__dir__}/scripts")
99
+ unless gem_available?('ruby-dbus')
100
+ # Ignore D-Bus interface/service files that require ruby-dbus,
101
+ # but keep pure-Ruby utilities (e.g. RateLimiter) available.
102
+ ['service', 'compositor_detector', 'focus_monitor_interface', 'manager_interface',
103
+ 'plugin_manager_interface', 'status_notifier_interface'].each do |f|
104
+ loader.ignore("#{__dir__}/rosett-ai/dbus/#{f}.rb")
105
+ end
106
+ loader.ignore("#{__dir__}/rosett-ai/dbus/focus_adapters/gnome_adapter.rb")
107
+ loader.ignore("#{__dir__}/rosett-ai/dbus/focus_adapters/kwin_adapter.rb")
108
+ loader.ignore("#{__dir__}/rosett-ai/desktop")
109
+ end
110
+ ignore_deprecated_stubs(loader)
111
+ loader.setup
112
+ loader
113
+ end
114
+ end
115
+
116
+ # Active project context (detects +.rosett-ai/+ marker).
117
+ #
118
+ # @return [RosettAi::ProjectContext]
119
+ def context
120
+ @context ||= ProjectContext.new
121
+ end
122
+
123
+ # Configuration root directory, scoped to the active project if present.
124
+ #
125
+ # @return [Pathname]
126
+ def conf_root
127
+ context.conf_root
128
+ end
129
+
130
+ # Global {Configuration} instance (settings.json hierarchy).
131
+ #
132
+ # @return [RosettAi::Configuration]
133
+ def config
134
+ @config ||= Configuration.new
135
+ end
136
+
137
+ # Global {PathResolver} instance for filesystem path lookups.
138
+ #
139
+ # @return [RosettAi::PathResolver]
140
+ def paths
141
+ @paths ||= PathResolver.new
142
+ end
143
+
144
+ # Loaded +~/.config/rosett-ai/config.yml+ settings.
145
+ #
146
+ # @return [RosettAi::RaiConfig]
147
+ def rai_config
148
+ @rai_config ||= RaiConfig.load
149
+ end
150
+
151
+ # Initializes I18n with rosett-ai locale files, resolves the active locale,
152
+ # and configures fallback chains (e.g. nl_BE -> nl -> en).
153
+ #
154
+ # @param locale [String, nil] explicit locale override (e.g. +"fr"+)
155
+ # @return [void]
156
+ def setup_i18n(locale: nil)
157
+ require 'i18n/backend/fallbacks'
158
+ ::I18n::Backend::Simple.include(::I18n::Backend::Fallbacks)
159
+
160
+ ::I18n.load_path += Dir[root.join('locales', '*.yml')]
161
+ ::I18n.default_locale = :en
162
+ ::I18n.enforce_available_locales = false
163
+
164
+ resolved_locale = resolve_locale(locale)
165
+ ::I18n.locale = resolved_locale.to_sym
166
+ end
167
+
168
+ # Structured logger with correlation IDs and JSON/text output.
169
+ # Level controlled by +RAI_LOG_LEVEL+, format by +RAI_LOG_FORMAT+.
170
+ #
171
+ # @return [RosettAi::StructuredLogger]
172
+ def logger
173
+ @logger ||= StructuredLogger.new
174
+ end
175
+
176
+ # Resets all memoized singletons. Intended for test isolation.
177
+ #
178
+ # @return [void]
179
+ def reset_config!
180
+ @config = nil
181
+ @paths = nil
182
+ @rai_config = nil
183
+ @logger = nil
184
+ @context = nil
185
+ end
186
+
187
+ private
188
+
189
+ def resolve_locale(explicit)
190
+ resolver = RosettAi::I18n::LocaleResolver.new
191
+ result = resolver.resolve(explicit: explicit)
192
+ available = ::I18n.available_locales.map(&:to_s)
193
+ locale = result[:chain].find { |loc| available.include?(loc) } || 'en'
194
+ chain = resolver.fallback_chain(locale).map(&:to_sym)
195
+ ::I18n.fallbacks = ::I18n::Locale::Fallbacks.new(locale.to_sym => chain)
196
+ locale
197
+ end
198
+
199
+ # Deprecated delegation stubs reference engine gem constants
200
+ # (RosettAiEngine::Claude::*, RosettAiEngine::Generic::*) that are only available
201
+ # when engine gems are installed. Ignoring them from Zeitwerk prevents
202
+ # NameError on autoload. Will be removed in v1.1.0.
203
+ def ignore_deprecated_stubs(loader)
204
+ ['compiler/backends/claude_backend', 'compiler/backends/generic_backend',
205
+ 'config/compiler', 'config/key_map', 'config/scope_router'].each do |stub|
206
+ loader.ignore("#{__dir__}/rosett-ai/#{stub}.rb")
207
+ end
208
+ end
209
+
210
+ def gem_available?(name)
211
+ Gem::Specification.find_by_name(name)
212
+ true
213
+ rescue Gem::MissingSpecError
214
+ false
215
+ end
216
+ end
217
+ end
218
+
219
+ RosettAi.loader
220
+ RosettAi.setup_i18n
221
+ RosettAi::Plugins::Registry.discover!
@@ -0,0 +1,54 @@
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 RuboCop
7
+ module Cop
8
+ module RosettAi
9
+ # Flags `system`, `exec`, and `spawn` calls where the first argument
10
+ # is an interpolated string, and backtick commands with interpolation.
11
+ # Both patterns risk shell injection.
12
+ #
13
+ # Use array-form instead: `system('cmd', arg1, arg2)` or
14
+ # `IO.popen([cmd, arg], &:read)`.
15
+ #
16
+ # @example
17
+ # # bad
18
+ # system("rm #{filename}")
19
+ # exec("echo #{user_input}")
20
+ # spawn("process #{data}")
21
+ # `#{cmd} --flag`
22
+ #
23
+ # # good
24
+ # system('rm', filename)
25
+ # system(ENV.fetch('EDITOR', 'vim'), file)
26
+ # IO.popen([cmd, '--flag'], &:read)
27
+ # `ls -la`
28
+ class ShellInterpolation < Base
29
+ MSG = 'Use array-form `system(cmd, arg, ...)` instead of interpolated string to avoid shell injection.'
30
+ BACKTICK_MSG = 'Use `IO.popen([cmd, arg], &:read)` instead of backtick interpolation to avoid shell injection.'
31
+
32
+ SHELL_METHODS = [:system, :exec, :spawn].freeze
33
+
34
+ # system/exec/spawn with a dstr (interpolated string) as first argument
35
+ # @!method shell_with_interpolation?(node)
36
+ def_node_matcher :shell_with_interpolation?, <<~PATTERN
37
+ (send nil? {#{SHELL_METHODS.map { |method| ":#{method}" }.join(' ')}} dstr ...)
38
+ PATTERN
39
+
40
+ def on_send(node)
41
+ return unless shell_with_interpolation?(node)
42
+
43
+ add_offense(node)
44
+ end
45
+
46
+ def on_xstr(node)
47
+ return unless node.children.any?(&:begin_type?)
48
+
49
+ add_offense(node, message: BACKTICK_MSG)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-License-Identifier: GPL-3.0-only
4
+ # Copyright (C) 2026 Hugo Antonio Sepulveda Manriquez / NeatNerds
5
+
6
+ module RuboCop
7
+ module Cop
8
+ module RosettAi
9
+ # Flags `const_get` and `constantize` calls with non-literal arguments.
10
+ # Dynamic constant lookup with user input can lead to arbitrary class
11
+ # instantiation and code execution.
12
+ #
13
+ # Safe patterns use frozen constant registries that map user input to
14
+ # allowed class names.
15
+ #
16
+ # @example
17
+ # # bad
18
+ # Object.const_get(user_input)
19
+ # Kernel.const_get(params[:class])
20
+ # user_input.constantize
21
+ #
22
+ # # good - use a frozen allowlist
23
+ # ALLOWED_CLASSES = { 'foo' => Foo, 'bar' => Bar }.freeze
24
+ # ALLOWED_CLASSES.fetch(user_input)
25
+ #
26
+ # # good - literal constant names
27
+ # Object.const_get(:MyClass)
28
+ # Object.const_get('RosettAi::Parser')
29
+ class UnsafeConstGet < Base
30
+ MSG = 'Avoid `const_get` with non-literal arguments. ' \
31
+ 'Use a frozen allowlist instead to prevent arbitrary class instantiation.'
32
+ CONSTANTIZE_MSG = 'Avoid `constantize` on dynamic strings. ' \
33
+ 'Use a frozen allowlist instead.'
34
+
35
+ CONST_GET_METHODS = [:const_get].freeze
36
+ CONSTANTIZE_METHODS = [:constantize, :safe_constantize].freeze
37
+
38
+ # const_get with any argument that is not a literal symbol or string
39
+ # @!method const_get_with_dynamic?(node)
40
+ def_node_matcher :const_get_with_dynamic?, <<~PATTERN
41
+ (send _ {:const_get} $!{sym str})
42
+ PATTERN
43
+
44
+ # .constantize or .safe_constantize on any receiver
45
+ # @!method constantize_call?(node)
46
+ def_node_matcher :constantize_call?, <<~PATTERN
47
+ (send _ {:constantize :safe_constantize})
48
+ PATTERN
49
+
50
+ def on_send(node)
51
+ if const_get_with_dynamic?(node)
52
+ add_offense(node)
53
+ elsif constantize_call?(node)
54
+ add_offense(node, message: CONSTANTIZE_MSG)
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ 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 RuboCop
7
+ module Cop
8
+ module RosettAi
9
+ # Flags `send`, `public_send`, and `__send__` calls with non-literal
10
+ # method names. Dynamic method dispatch with user input can lead to
11
+ # arbitrary method execution.
12
+ #
13
+ # Safe patterns validate the method name against an allowlist before
14
+ # calling send.
15
+ #
16
+ # @example
17
+ # # bad
18
+ # object.send(user_input)
19
+ # object.public_send(params[:method])
20
+ # receiver.__send__(dynamic_method)
21
+ #
22
+ # # good - validate against allowlist first
23
+ # ALLOWED_METHODS = %w[foo bar].freeze
24
+ # raise ArgumentError unless ALLOWED_METHODS.include?(method_name)
25
+ # object.send(method_name)
26
+ #
27
+ # # good - literal method names
28
+ # object.send(:known_method)
29
+ # object.public_send('safe_method')
30
+ class UnsafeSend < Base
31
+ MSG = 'Avoid `%<method>s` with non-literal method names. ' \
32
+ 'Validate against an allowlist first to prevent arbitrary method execution.'
33
+
34
+ SEND_METHODS = [:send, :public_send, :__send__].freeze
35
+
36
+ # send/public_send/__send__ with any argument that is not a literal symbol or string
37
+ # @!method send_with_dynamic?(node)
38
+ def_node_matcher :send_with_dynamic?, <<~PATTERN
39
+ (send _ ${:send :public_send :__send__} $!{sym str} ...)
40
+ PATTERN
41
+
42
+ def on_send(node)
43
+ send_with_dynamic?(node) do |method_name, _arg|
44
+ add_offense(node, message: format(MSG, method: method_name))
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,40 @@
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 RuboCop
7
+ module Cop
8
+ module RosettAi
9
+ # Flags calls to `YAML.load` which is unsafe for untrusted input.
10
+ #
11
+ # Use `YAML.safe_load`, `YAML.safe_load_file`, or
12
+ # `RosettAi::YamlLoader.load_file` instead.
13
+ #
14
+ # @example
15
+ # # bad
16
+ # YAML.load(string)
17
+ # YAML.load_file(path)
18
+ #
19
+ # # good
20
+ # YAML.safe_load(string)
21
+ # YAML.safe_load_file(path)
22
+ # RosettAi::YamlLoader.load_file(path)
23
+ class UnsafeYamlLoad < Base
24
+ MSG = 'Use `YAML.safe_load` or `RosettAi::YamlLoader.load_file` instead of `YAML.load`.'
25
+
26
+ # YAML.load(...) or YAML.load_file(...)
27
+ # @!method unsafe_yaml_load?(node)
28
+ def_node_matcher :unsafe_yaml_load?, <<~PATTERN
29
+ (send (const {nil? cbase} :YAML) {:load :load_file} ...)
30
+ PATTERN
31
+
32
+ def on_send(node)
33
+ return unless unsafe_yaml_load?(node)
34
+
35
+ add_offense(node)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,9 @@
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_relative 'cop/rosett_ai/unsafe_yaml_load'
7
+ require_relative 'cop/rosett_ai/shell_interpolation'
8
+ require_relative 'cop/rosett_ai/unsafe_const_get'
9
+ require_relative 'cop/rosett_ai/unsafe_send'