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,43 @@
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 Provenance
8
+ # Generates commit trailers from provenance entries.
9
+ #
10
+ # Produces standardised commit trailers in the format:
11
+ # Role: Tool Version (Provider) <email>
12
+ class TrailerGenerator
13
+ # Generates a commit trailer from a provenance entry.
14
+ #
15
+ # @param entry [Entry, Hash] provenance entry
16
+ # @return [String] formatted commit trailer
17
+ def generate(entry)
18
+ entry_hash = entry.is_a?(Hash) ? entry : entry.to_h
19
+ role = entry_hash['ai_role'] || entry_hash[:ai_role]
20
+ tool = entry_hash['ai_tool'] || entry_hash[:ai_tool]
21
+
22
+ "#{role}: #{tool}"
23
+ end
24
+
25
+ # Generates trailers for all entries in a provenance dataset.
26
+ #
27
+ # @param data [Hash] full provenance data with 'entries' key
28
+ # @return [Array<String>] unique trailers
29
+ def generate_all(data)
30
+ entries = data.fetch('entries', [])
31
+ entries.map { |entry| generate(entry) }.uniq
32
+ end
33
+
34
+ # Formats trailers as a block suitable for appending to a commit message.
35
+ #
36
+ # @param data [Hash] full provenance data
37
+ # @return [String] newline-separated trailer block
38
+ def trailer_block(data)
39
+ generate_all(data).join("\n")
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,45 @@
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 'json_schemer'
8
+
9
+ module RosettAi
10
+ module Provenance
11
+ # Validates provenance entries against the provenance JSON Schema.
12
+ class Validator
13
+ SCHEMA_PATH = File.join(RosettAi.root, 'conf', 'schemas', 'provenance_schema.json')
14
+
15
+ # Validates provenance data against the schema.
16
+ #
17
+ # @param data [Hash] provenance data (parsed YAML)
18
+ # @return [Array<String>] list of validation errors (empty if valid)
19
+ def validate(data)
20
+ schema = load_schema
21
+ schemer = JSONSchemer.schema(schema)
22
+ errors = schemer.validate(data)
23
+ errors.map { |error| format_error(error) }
24
+ end
25
+
26
+ # @param data [Hash] provenance data
27
+ # @return [Boolean] true if data is valid
28
+ def valid?(data)
29
+ validate(data).empty?
30
+ end
31
+
32
+ private
33
+
34
+ def load_schema
35
+ JSON.parse(File.read(SCHEMA_PATH))
36
+ end
37
+
38
+ def format_error(error)
39
+ path = error['data_pointer']
40
+ message = error['type']
41
+ "#{path}: #{message}"
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-License-Identifier: GPL-3.0-only
4
+ # Copyright (C) 2026 Hugo Antonio Sepulveda Manriquez / NeatNerds
5
+
6
+ module RosettAi
7
+ module Quorum
8
+ # Normalizes per-engine responses to the standard adopt contract:
9
+ # { findings: [...], overall_status: "pass|warn|fail", summary: "..." }
10
+ #
11
+ # Annotates each finding with its source engine.
12
+ class Collector
13
+ # Normalize dispatched results into per-engine response hashes.
14
+ #
15
+ # @param dispatched [Hash<String, Hash>] engine_name => dispatch result
16
+ # @return [Hash<String, Hash>] engine_name => normalized result
17
+ def collect(dispatched)
18
+ dispatched.transform_values { |dispatch_entry| normalize(dispatch_entry) }
19
+ end
20
+
21
+ private
22
+
23
+ def normalize(dispatch_entry)
24
+ engine_name = dispatch_entry[:engine]
25
+ error = dispatch_entry[:error]
26
+
27
+ return error_result(engine_name, error) if error
28
+
29
+ annotate_findings(engine_name, dispatch_entry[:result])
30
+ end
31
+
32
+ def annotate_findings(engine_name, result)
33
+ findings = (result['findings'] || []).map do |finding|
34
+ finding.merge('engine' => engine_name)
35
+ end
36
+
37
+ {
38
+ 'findings' => findings,
39
+ 'overall_status' => result['overall_status'] || 'pass',
40
+ 'summary' => result['summary'] || ''
41
+ }
42
+ end
43
+
44
+ def error_result(engine_name, error_message)
45
+ {
46
+ 'findings' => [{
47
+ 'category' => 'other', 'severity' => 'high',
48
+ 'file' => 'N/A', 'rule_id' => nil,
49
+ 'summary' => "Engine #{engine_name} failed",
50
+ 'explanation' => error_message,
51
+ 'engine' => engine_name
52
+ }],
53
+ 'overall_status' => 'fail',
54
+ 'summary' => "Engine #{engine_name} failed: #{error_message}"
55
+ }
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,81 @@
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 Quorum
8
+ # Aggregates findings from multiple engines.
9
+ #
10
+ # Cross-engine findings (same category + file + rule_id from 2+ engines)
11
+ # are promoted by severity. Unique findings are annotated with their
12
+ # source engine for transparency.
13
+ class Comparator
14
+ FAIL_SEVERITIES = ['high', 'critical'].freeze
15
+ SEVERITY_RANK = { 'low' => 0, 'medium' => 1, 'high' => 2, 'critical' => 3 }.freeze
16
+
17
+ # Compare and aggregate collected engine results.
18
+ #
19
+ # @param collected [Hash<String, Hash>] engine_name => normalized result
20
+ # @return [Hash] merged result with findings, overall_status, summary
21
+ def compare(collected)
22
+ all_findings = collected.values.flat_map { |r| r['findings'] }
23
+ grouped = group_findings(all_findings)
24
+ merged = grouped.map { |_key, group| merge_group(group) }
25
+
26
+ {
27
+ 'findings' => merged,
28
+ 'overall_status' => determine_status(merged),
29
+ 'summary' => build_summary(merged, collected.keys)
30
+ }
31
+ end
32
+
33
+ private
34
+
35
+ def group_findings(findings)
36
+ findings.group_by { |f| finding_key(f) }
37
+ end
38
+
39
+ def finding_key(finding)
40
+ [finding['category'], finding['file'], finding['rule_id']].join('|')
41
+ end
42
+
43
+ def merge_group(group)
44
+ engines = extract_engines(group)
45
+ merged = highest_severity_finding(group)
46
+ merged['engines'] = engines
47
+ merged['cross_engine'] = engines.size > 1
48
+ merged
49
+ end
50
+
51
+ def extract_engines(group)
52
+ group.map { |entry| entry['engine'] }.uniq
53
+ end
54
+
55
+ def highest_severity_finding(group)
56
+ group.max_by { |entry| SEVERITY_RANK.fetch(entry['severity'], -1) }.dup
57
+ end
58
+
59
+ def determine_status(findings)
60
+ if findings.any? { |f| FAIL_SEVERITIES.include?(f['severity']) }
61
+ 'fail'
62
+ elsif findings.any?
63
+ 'warn'
64
+ else
65
+ 'pass'
66
+ end
67
+ end
68
+
69
+ def build_summary(findings, engine_names)
70
+ return "No issues found across #{engine_names.size} engine(s)" if findings.empty?
71
+
72
+ cross = findings.count { |f| f['cross_engine'] }
73
+ unique = findings.size - cross
74
+ parts = ["#{findings.size} finding(s) across #{engine_names.size} engine(s)"]
75
+ parts << "#{cross} cross-engine" if cross.positive?
76
+ parts << "#{unique} unique" if unique.positive?
77
+ parts.join(', ')
78
+ end
79
+ end
80
+ end
81
+ end
@@ -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 Quorum
8
+ # Dispatches a prompt to multiple engine executors in parallel.
9
+ #
10
+ # Uses Thread (not Ractor) because the anthropic gem and Net::HTTP
11
+ # are not Ractor-safe. Thread is sufficient for I/O-bound API calls.
12
+ class Dispatcher
13
+ # Dispatch a prompt to the given executors.
14
+ #
15
+ # @param prompt [String] the analysis prompt
16
+ # @param executors [Hash<String, Object>] engine_name => executor pairs
17
+ # @return [Hash<String, Hash>] engine_name => {result:, error:, elapsed:}
18
+ def dispatch(prompt, executors)
19
+ threads = spawn_threads(prompt, executors)
20
+ collect_thread_results(threads)
21
+ end
22
+
23
+ private
24
+
25
+ def spawn_threads(prompt, executors)
26
+ executors.map do |engine_name, executor|
27
+ Thread.new(engine_name, executor) do |name, exec|
28
+ Thread.current[:engine] = name
29
+ dispatch_single(name, exec, prompt)
30
+ end
31
+ end
32
+ end
33
+
34
+ def dispatch_single(engine_name, executor, prompt)
35
+ start = monotonic_now
36
+ result = executor.analyze(prompt)
37
+
38
+ { engine: engine_name, result: result, error: nil, elapsed: monotonic_now - start }
39
+ rescue StandardError => e
40
+ { engine: engine_name, result: nil, error: e.message, elapsed: monotonic_now - start }
41
+ end
42
+
43
+ def monotonic_now
44
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
45
+ end
46
+
47
+ def collect_thread_results(threads)
48
+ results = {}
49
+ threads.each do |thread|
50
+ thread.join
51
+ results[thread[:engine]] = thread.value
52
+ end
53
+ results
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,56 @@
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 Quorum
8
+ module Strategies
9
+ # Orchestrates multi-engine adopt analysis:
10
+ # 1. Detect available engines with adopt_api capability
11
+ # 2. Resolve executors via ExecutorResolver
12
+ # 3. Dispatch prompt to all executors in parallel
13
+ # 4. Collect and compare results
14
+ class Adopt
15
+ def initialize(api_key: nil)
16
+ @api_key = api_key
17
+ end
18
+
19
+ # Run quorum adopt analysis.
20
+ #
21
+ # @param prompt [String] the analysis prompt
22
+ # @return [Hash] aggregated result with findings, overall_status, summary
23
+ def run(prompt)
24
+ executors = resolve_executors
25
+ raise RosettAi::AdoptError, 'No engines with adopt_api capability detected' if executors.empty?
26
+
27
+ dispatched = Dispatcher.new.dispatch(prompt, executors)
28
+ collected = Collector.new.collect(dispatched)
29
+ Comparator.new.compare(collected)
30
+ end
31
+
32
+ # List engines eligible for quorum adopt.
33
+ #
34
+ # @return [Array<String>] engine names
35
+ def eligible_engines
36
+ RosettAi::Engines::Registry.available.select do |name|
37
+ manifest = RosettAi::Engines::Registry.manifest(name)
38
+ manifest.dig('capabilities', 'adopt_api') == true &&
39
+ manifest.dig('components', 'executor') == true
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def resolve_executors
46
+ eligible_engines.each_with_object({}) do |name, hash|
47
+ resolver = RosettAi::Adopter::ExecutorResolver.new(name)
48
+ hash[name] = resolver.resolve(api_key: @api_key)
49
+ rescue RosettAi::AdoptError => e
50
+ RosettAi.logger.warn("Skipping engine #{name} for quorum: #{e.message}")
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,107 @@
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_schemer'
7
+
8
+ module RosettAi
9
+ # Loads and validates Rosett-AI runtime configuration from ~/.config/rosett-ai/config.yml.
10
+ #
11
+ # Returns compiled defaults when no config file exists. User values are
12
+ # deep-merged over defaults and validated against the rai config JSON Schema.
13
+ class RaiConfig
14
+ DEFAULTS = {
15
+ 'default_engine' => 'claude',
16
+ 'cache' => { 'enabled' => true, 'ttl_hours' => 24 },
17
+ 'detect' => { 'on_init' => true },
18
+ 'dbus' => { 'allow_plugin_management' => false }
19
+ }.freeze
20
+
21
+ attr_reader :config_path
22
+
23
+ def initialize(config_path: nil)
24
+ @config_path = config_path || RosettAi.paths.rai_config_dir.join('config.yml')
25
+ @data = load_config
26
+ end
27
+
28
+ def default_engine = @data['default_engine']
29
+ def cache_enabled? = @data.dig('cache', 'enabled')
30
+ def cache_ttl_hours = @data.dig('cache', 'ttl_hours')
31
+ def detect_on_init? = @data.dig('detect', 'on_init')
32
+ def dbus_allow_plugin_management? = @data.dig('dbus', 'allow_plugin_management') == true
33
+
34
+ def self.load(config_path: nil)
35
+ new(config_path: config_path)
36
+ end
37
+
38
+ # Update a configuration key and persist to disk.
39
+ #
40
+ # @param key [String] dot-separated key (e.g. 'default_engine', 'cache.enabled')
41
+ # @param value [String] new value (coerced to boolean/integer where appropriate)
42
+ def update(key, value)
43
+ coerced = coerce_value(value)
44
+ set_nested_key(@data, key, coerced)
45
+ validate!(@data)
46
+ persist!
47
+ end
48
+
49
+ private
50
+
51
+ def load_config
52
+ return DEFAULTS.dup unless @config_path.exist?
53
+
54
+ user_data = RosettAi::YamlLoader.load_file(@config_path.to_s)
55
+ merged = deep_merge(DEFAULTS, user_data || {})
56
+ validate!(merged)
57
+ merged
58
+ end
59
+
60
+ def validate!(data)
61
+ schema_path = RosettAi.root.join('conf', 'schemas', 'rai_config_schema.json')
62
+ schema = JSON.parse(schema_path.read)
63
+ schemer = JSONSchemer.schema(schema)
64
+ errors = schemer.validate(data).to_a
65
+
66
+ return if errors.empty?
67
+
68
+ messages = errors.map { |e| "#{e['data_pointer']}: #{e['type']}" }
69
+ raise RosettAi::ConfigurationError,
70
+ "Invalid rai config at #{@config_path}: #{messages.join(', ')}"
71
+ end
72
+
73
+ def persist!
74
+ FileUtils.mkdir_p(@config_path.dirname)
75
+ File.write(@config_path, YAML.dump(@data))
76
+ File.chmod(0o600, @config_path)
77
+ end
78
+
79
+ def coerce_value(value)
80
+ case value
81
+ when 'true' then true
82
+ when 'false' then false
83
+ when /\A\d+\z/ then value.to_i
84
+ else value
85
+ end
86
+ end
87
+
88
+ def set_nested_key(data, key, value)
89
+ parts = key.split('.')
90
+ target = parts[0..-2].reduce(data) { |hash, k| hash[k] ||= {} }
91
+ target[parts.last] = value
92
+ end
93
+
94
+ def deep_merge(base, overlay)
95
+ base.each_with_object(overlay.dup) do |(key, base_val), result|
96
+ overlay_val = result[key]
97
+ result[key] = if result.key?(key) && base_val.is_a?(Hash) && overlay_val.is_a?(Hash)
98
+ deep_merge(base_val, overlay_val)
99
+ elsif result.key?(key)
100
+ overlay_val
101
+ else
102
+ base_val.is_a?(Hash) ? base_val.dup : base_val
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-License-Identifier: GPL-3.0-only
4
+ # Copyright (C) 2026 Hugo Antonio Sepulveda Manriquez / NeatNerds
5
+
6
+ module RosettAi
7
+ module Retrofit
8
+ # Abstract base class for engine-specific retrofit parsers.
9
+ #
10
+ # Each engine provides a parser that knows how to discover and read
11
+ # native config files, convert them to Rosett-AI YAML structure, and
12
+ # identify unknown keys.
13
+ #
14
+ # @abstract Subclasses must implement {#discover}, {#parse}, and {#engine_name}.
15
+ # @author hugo
16
+ # @author claude
17
+ class BaseParser
18
+ # @return [String] engine identifier
19
+ def engine_name
20
+ raise NotImplementedError, "#{self.class}#engine_name must be implemented"
21
+ end
22
+
23
+ # Discovers native config files for this engine.
24
+ #
25
+ # @return [Array<Pathname>] list of discovered config file paths
26
+ def discover
27
+ raise NotImplementedError, "#{self.class}#discover must be implemented"
28
+ end
29
+
30
+ # Parses a native config file into Rosett-AI YAML structure.
31
+ #
32
+ # @param path [Pathname] path to the native config file
33
+ # @return [Hash] parsed configuration data with :data, :unknown_keys, :source_file keys
34
+ def parse(path)
35
+ raise NotImplementedError, "#{self.class}#parse must be implemented"
36
+ end
37
+
38
+ # @return [Boolean] true if this engine's native config is present
39
+ def available?
40
+ discover.any?
41
+ end
42
+
43
+ private
44
+
45
+ # Validates that a path is safe to read (within HOME, no symlink escape).
46
+ #
47
+ # @param path [Pathname] path to validate
48
+ # @return [Boolean] true if path is safe
49
+ def safe_path?(path)
50
+ home = Pathname.new(Dir.home)
51
+ resolved = path.realpath
52
+ resolved.to_s.start_with?(home.to_s)
53
+ rescue Errno::ENOENT
54
+ false
55
+ end
56
+
57
+ # Strips ANSI escape sequences and control characters from a string.
58
+ #
59
+ # @param text [String] input text
60
+ # @return [String] sanitized text
61
+ def sanitize(text)
62
+ text.to_s.gsub(/\e\[[0-9;]*[a-zA-Z]/, '').gsub(/[\x00-\x08\x0B\x0C\x0E-\x1F]/, '')
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,171 @@
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
+ require 'fileutils'
8
+
9
+ module RosettAi
10
+ module Retrofit
11
+ # Orchestrates reverse compilation from native AI tool configs to Rosett-AI YAML.
12
+ #
13
+ # Discovers installed engines, parses native configs, redacts secrets,
14
+ # and writes rosett-ai-compatible YAML source files.
15
+ #
16
+ # @author hugo
17
+ # @author claude
18
+ class Engine
19
+ PARSER_REGISTRY = {
20
+ 'claude' => 'RosettAi::Retrofit::Parsers::ClaudeParser',
21
+ 'cursor' => 'RosettAi::Retrofit::Parsers::CursorParser',
22
+ 'agents_md' => 'RosettAi::Retrofit::Parsers::AgentsMdParser'
23
+ }.freeze
24
+
25
+ # @param output_dir [Pathname] directory to write generated YAML
26
+ # @param engines [Array<String>, nil] specific engines or nil for auto-detect
27
+ # @param force [Boolean] overwrite existing files
28
+ def initialize(output_dir:, engines: nil, force: false)
29
+ @output_dir = output_dir
30
+ @requested_engines = engines
31
+ @force = force
32
+ @warnings = []
33
+ @secret_detector = SecretDetector.new(warnings: @warnings)
34
+ end
35
+
36
+ # Performs the retrofit operation.
37
+ #
38
+ # @return [Hash] result with :generated, :skipped, :errors, :warnings keys
39
+ def retrofit
40
+ results = { generated: [], skipped: [], errors: [], warnings: [] }
41
+ parsers = resolve_parsers(results)
42
+
43
+ parsers.each { |parser| retrofit_engine(parser, results) }
44
+
45
+ write_manifest(results) unless results[:generated].empty?
46
+ results[:warnings] = @warnings
47
+ results
48
+ end
49
+
50
+ # Simulates retrofit without writing files.
51
+ #
52
+ # @return [Hash] same structure as retrofit but read-only
53
+ def simulate
54
+ results = { generated: [], skipped: [], errors: [], warnings: [] }
55
+ parsers = resolve_parsers(results)
56
+
57
+ parsers.each { |parser| simulate_engine(parser, results) }
58
+
59
+ results[:warnings] = @warnings
60
+ results
61
+ end
62
+
63
+ # @return [Array<String>] list of available engine names
64
+ def self.available_engines
65
+ PARSER_REGISTRY.keys
66
+ end
67
+
68
+ private
69
+
70
+ def resolve_parsers(results)
71
+ if @requested_engines
72
+ @requested_engines.filter_map do |name|
73
+ klass_name = PARSER_REGISTRY[name]
74
+ unless klass_name
75
+ results[:errors] << "No retrofit adapter for engine '#{name}'"
76
+ next
77
+ end
78
+ Object.const_get(klass_name).new
79
+ end
80
+ else
81
+ auto_detect_parsers
82
+ end
83
+ end
84
+
85
+ def auto_detect_parsers
86
+ PARSER_REGISTRY.filter_map do |_name, klass_name|
87
+ parser = Object.const_get(klass_name).new
88
+ parser if parser.available?
89
+ end
90
+ end
91
+
92
+ def retrofit_engine(parser, results)
93
+ parser.discover.each do |path|
94
+ parsed = parser.parse(path)
95
+ redacted = @secret_detector.redact(parsed[:data])
96
+ output_path = resolve_output_path(parser.engine_name, parsed[:scope])
97
+
98
+ write_yaml(output_path, redacted, parsed, results)
99
+ rescue RosettAi::RetrofitError => e
100
+ results[:errors] << "#{parser.engine_name}: #{e.message}"
101
+ end
102
+ end
103
+
104
+ def simulate_engine(parser, results)
105
+ parser.discover.each do |path|
106
+ parsed = parser.parse(path)
107
+ @secret_detector.redact(parsed[:data])
108
+ output_path = resolve_output_path(parser.engine_name, parsed[:scope])
109
+
110
+ if output_path.exist? && !@force
111
+ results[:skipped] << output_path.to_s
112
+ else
113
+ results[:generated] << output_path.to_s
114
+ end
115
+ rescue RosettAi::RetrofitError => e
116
+ results[:errors] << "#{parser.engine_name}: #{e.message}"
117
+ end
118
+ end
119
+
120
+ def resolve_output_path(engine_name, scope)
121
+ @output_dir.join('conf', engine_name, "#{scope}.yml")
122
+ end
123
+
124
+ def write_yaml(path, data, parsed, results)
125
+ if path.exist? && !@force
126
+ results[:skipped] << path.to_s
127
+ return
128
+ end
129
+
130
+ path.dirname.mkpath
131
+ content = build_yaml_content(data, parsed)
132
+ File.write(path, content)
133
+ results[:generated] << path.to_s
134
+ end
135
+
136
+ def build_yaml_content(data, parsed)
137
+ lines = []
138
+ lines << "# Retrofitted from: #{parsed[:source_file]}"
139
+ lines << "# Engine: #{parsed.fetch(:scope, 'unknown')}"
140
+ lines << "# Date: #{Time.now.utc.iso8601}"
141
+ lines << "# rosett-ai version: #{RosettAi::VERSION}"
142
+
143
+ unknown = parsed[:unknown_keys]
144
+ unless unknown.empty?
145
+ lines << '#'
146
+ lines << '# Unknown keys (not in rosett-ai schema, preserved for compatibility):'
147
+ unknown.each { |key| lines << "# - #{key}" }
148
+ end
149
+
150
+ lines << '---'
151
+ lines << YAML.dump(data).delete_prefix("---\n")
152
+ lines.join("\n")
153
+ end
154
+
155
+ def write_manifest(results)
156
+ manifest_path = @output_dir.join('conf', 'retrofit_manifest.yml')
157
+ manifest_path.dirname.mkpath
158
+
159
+ manifest = {
160
+ 'retrofitted_at' => Time.now.utc.iso8601,
161
+ 'rai_version' => RosettAi::VERSION,
162
+ 'sources' => results[:generated].map do |path|
163
+ { 'output' => path, 'engine' => File.basename(File.dirname(path)) }
164
+ end
165
+ }
166
+
167
+ File.write(manifest_path, YAML.dump(manifest))
168
+ end
169
+ end
170
+ end
171
+ end