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,250 @@
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 'fileutils'
7
+ require 'time'
8
+
9
+ module RosettAi
10
+ module Compiler
11
+ # Compiles ruby-i18n YAML locale files into gettext (.pot/.po) and
12
+ # Qt Linguist (.ts) formats.
13
+ #
14
+ # Reads all *.yml files from the source directory, flattens nested
15
+ # keys into dotted paths, and generates platform-native locale files
16
+ # for distribution.
17
+ class LocaleCompiler
18
+ PLURAL_FORMS = ['zero', 'one', 'two', 'few', 'many', 'other'].freeze
19
+
20
+ PLURAL_HEADERS = {
21
+ 'en' => 'nplurals=2; plural=(n != 1);',
22
+ 'fr' => 'nplurals=2; plural=(n > 1);',
23
+ 'ar' => 'nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 ? 4 : 5);'
24
+ }.freeze
25
+
26
+ attr_reader :source_dir, :gettext_dir, :qt_dir
27
+
28
+ # Initialize the locale compiler with source and output directories.
29
+ #
30
+ # @param source_dir [String, Pathname] locale YAML directory
31
+ # @param gettext_dir [String, Pathname] gettext output directory
32
+ # @param qt_dir [String, Pathname] Qt .ts output directory
33
+ def initialize(source_dir:, gettext_dir:, qt_dir:)
34
+ @source_dir = Pathname.new(source_dir)
35
+ @gettext_dir = Pathname.new(gettext_dir)
36
+ @qt_dir = Pathname.new(qt_dir)
37
+ end
38
+
39
+ # Compiles all locale YAML files to gettext and Qt Linguist formats.
40
+ #
41
+ # @return [Hash{Symbol => Array<String>}] output paths keyed by :gettext and :qt
42
+ def compile
43
+ locale_files = discover_locale_files
44
+ results = { gettext: [], qt: [] }
45
+
46
+ locale_files.each do |file|
47
+ data = RosettAi::YamlLoader.load_file(file)
48
+ locale = data.keys.first
49
+ entries = flatten_keys(data[locale])
50
+
51
+ results[:gettext] << compile_gettext(locale, entries)
52
+ results[:qt] << compile_qt(locale, entries)
53
+ end
54
+
55
+ results
56
+ end
57
+
58
+ private
59
+
60
+ def discover_locale_files
61
+ Dir.glob(source_dir.join('*.yml'))
62
+ end
63
+
64
+ def flatten_keys(data, prefix = nil)
65
+ entries = []
66
+ data.each do |key, value|
67
+ full_key = prefix ? "#{prefix}.#{key}" : key
68
+ case value
69
+ when Hash
70
+ if plural_hash?(value)
71
+ entries << { key: full_key, value: value, plural: true }
72
+ else
73
+ entries.concat(flatten_keys(value, full_key))
74
+ end
75
+ when String
76
+ entries << { key: full_key, value: value }
77
+ end
78
+ end
79
+ entries
80
+ end
81
+
82
+ def plural_hash?(hash)
83
+ hash.keys.any? { |k| PLURAL_FORMS.include?(k) }
84
+ end
85
+
86
+ def compile_gettext(locale, entries)
87
+ FileUtils.mkdir_p(gettext_dir)
88
+
89
+ pot_path = gettext_dir.join('rosett_ai.pot')
90
+ write_pot(pot_path, entries)
91
+
92
+ locale_dir = gettext_dir.join(locale, 'LC_MESSAGES')
93
+ FileUtils.mkdir_p(locale_dir)
94
+ po_path = locale_dir.join('rosett_ai.po')
95
+ write_po(po_path, locale, entries)
96
+
97
+ po_path.to_s
98
+ end
99
+
100
+ def compile_qt(locale, entries)
101
+ FileUtils.mkdir_p(qt_dir)
102
+
103
+ ts_path = qt_dir.join("rosett_ai_#{locale}.ts")
104
+ write_ts(ts_path, locale, entries)
105
+
106
+ ts_path.to_s
107
+ end
108
+
109
+ def write_pot(path, entries)
110
+ File.open(path, 'w', 0o644) do |file|
111
+ file.puts pot_header
112
+ entries.each do |entry|
113
+ file.puts
114
+ file.puts "#: #{entry[:key]}"
115
+ if entry[:plural]
116
+ singular = entry[:value]['one'] || entry[:value].values.first
117
+ plural = entry[:value]['other'] || singular
118
+ file.puts "msgid #{quote_po(singular)}"
119
+ file.puts "msgid_plural #{quote_po(plural)}"
120
+ file.puts 'msgstr[0] ""'
121
+ file.puts 'msgstr[1] ""'
122
+ else
123
+ file.puts "msgid #{quote_po(entry[:value])}"
124
+ file.puts 'msgstr ""'
125
+ end
126
+ end
127
+ end
128
+ end
129
+
130
+ def write_po(path, locale, entries)
131
+ File.open(path, 'w', 0o644) do |file|
132
+ file.puts po_header(locale)
133
+ entries.each do |entry|
134
+ file.puts
135
+ file.puts "#: #{entry[:key]}"
136
+ if entry[:plural]
137
+ write_po_plural(file, entry)
138
+ else
139
+ file.puts "msgid #{quote_po(entry[:value])}"
140
+ file.puts "msgstr #{quote_po(entry[:value])}"
141
+ end
142
+ end
143
+ end
144
+ end
145
+
146
+ def write_po_plural(file, entry)
147
+ forms = entry[:value]
148
+ singular = forms['one'] || forms.values.first
149
+ plural = forms['other'] || singular
150
+ file.puts "msgid #{quote_po(singular)}"
151
+ file.puts "msgid_plural #{quote_po(plural)}"
152
+ forms.each_with_index do |(_, form_text), idx|
153
+ file.puts "msgstr[#{idx}] #{quote_po(form_text)}"
154
+ end
155
+ end
156
+
157
+ def write_ts(path, locale, entries)
158
+ contexts = group_by_context(entries)
159
+
160
+ File.open(path, 'w', 0o644) do |file|
161
+ file.puts '<?xml version="1.0" encoding="utf-8"?>'
162
+ file.puts '<!DOCTYPE TS>'
163
+ file.puts "<TS version=\"2.1\" language=\"#{escape_xml(locale)}\">"
164
+
165
+ contexts.each do |context_name, messages|
166
+ file.puts ' <context>'
167
+ file.puts " <name>#{escape_xml(context_name)}</name>"
168
+ messages.each { |msg| write_ts_message(file, msg) }
169
+ file.puts ' </context>'
170
+ end
171
+
172
+ file.puts '</TS>'
173
+ end
174
+ end
175
+
176
+ def write_ts_message(file, msg)
177
+ if msg[:plural]
178
+ file.puts ' <message numerus="yes">'
179
+ file.puts " <source>#{escape_xml(msg[:key])}</source>"
180
+ file.puts ' <translation>'
181
+ msg[:value].each_value { |text| file.puts " <numerusform>#{escape_xml(text)}</numerusform>" }
182
+ file.puts ' </translation>'
183
+ else
184
+ file.puts ' <message>'
185
+ file.puts " <source>#{escape_xml(msg[:key])}</source>"
186
+ file.puts " <translation>#{escape_xml(msg[:value])}</translation>"
187
+ end
188
+ file.puts ' </message>'
189
+ end
190
+
191
+ def group_by_context(entries)
192
+ groups = {}
193
+ entries.each do |entry|
194
+ parts = entry[:key].split('.')
195
+ context = parts.length > 1 ? parts[0...-1].join('.') : 'default'
196
+ groups[context] ||= []
197
+ groups[context] << entry
198
+ end
199
+ groups
200
+ end
201
+
202
+ def pot_header
203
+ <<~HEADER.chomp
204
+ # SOME DESCRIPTIVE TITLE.
205
+ # Copyright (C) 2026 NeatNerds
206
+ # This file is distributed under the GPL-3.0-only license.
207
+ #
208
+ msgid ""
209
+ msgstr ""
210
+ "Project-Id-Version: rosett-ai #{RosettAi::VERSION}\\n"
211
+ "POT-Creation-Date: #{Time.now.utc.strftime('%Y-%m-%d %H:%M%z')}\\n"
212
+ "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n"
213
+ "Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n"
214
+ "Language-Team: LANGUAGE <LL@li.org>\\n"
215
+ "MIME-Version: 1.0\\n"
216
+ "Content-Type: text/plain; charset=UTF-8\\n"
217
+ "Content-Transfer-Encoding: 8bit\\n"
218
+ HEADER
219
+ end
220
+
221
+ def po_header(locale)
222
+ plural_forms = PLURAL_HEADERS.fetch(locale, 'nplurals=2; plural=(n != 1);')
223
+ <<~HEADER.chomp
224
+ # #{locale.upcase} translations for Rosett-AI.
225
+ # Copyright (C) 2026 NeatNerds
226
+ # This file is distributed under the GPL-3.0-only license.
227
+ #
228
+ msgid ""
229
+ msgstr ""
230
+ "Project-Id-Version: rosett-ai #{RosettAi::VERSION}\\n"
231
+ "PO-Revision-Date: #{Time.now.utc.strftime('%Y-%m-%d %H:%M%z')}\\n"
232
+ "Last-Translator: rosett-ai\\n"
233
+ "Language: #{locale}\\n"
234
+ "MIME-Version: 1.0\\n"
235
+ "Content-Type: text/plain; charset=UTF-8\\n"
236
+ "Content-Transfer-Encoding: 8bit\\n"
237
+ "Plural-Forms: #{plural_forms}\\n"
238
+ HEADER
239
+ end
240
+
241
+ def quote_po(string)
242
+ "\"#{string.gsub('\\', '\\\\\\\\').gsub('"', '\\"').gsub("\n", '\\n')}\""
243
+ end
244
+
245
+ def escape_xml(string)
246
+ string.gsub('&', '&amp;').gsub('<', '&lt;').gsub('>', '&gt;').gsub('"', '&quot;')
247
+ end
248
+ end
249
+ end
250
+ end
@@ -0,0 +1,112 @@
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 Compiler
11
+ # Loads and validates target profiles from conf/engines/<name>/target.yml,
12
+ # with fallback to conf/targets/<name>.yml for backward compatibility.
13
+ #
14
+ # Target profiles declare which backend to use, the output format, and
15
+ # rendering options for a specific AI assistant target. Adding a new
16
+ # target requires only a new YAML file — no code changes.
17
+ class TargetProfile
18
+ FIELDS = [
19
+ :name, :description, :backend, :engine_version, :output_format,
20
+ :output_dir, :max_context_window, :rendering
21
+ ].freeze
22
+
23
+ attr_reader(*FIELDS)
24
+
25
+ def initialize(data)
26
+ FIELDS.each do |field|
27
+ instance_variable_set(:"@#{field}", data[field.to_s])
28
+ end
29
+ end
30
+
31
+ # Load and validate a target profile by name.
32
+ #
33
+ # Resolution order:
34
+ # 1. Plugin registry (engine gem's `target_profile_path`)
35
+ # 2. `conf/engines/<name>/target.yml` (core tree)
36
+ # 3. `conf/targets/<name>.yml` (legacy)
37
+ #
38
+ # @param target_name [String] engine/target identifier
39
+ # @return [TargetProfile]
40
+ # @raise [RosettAi::CompileError] if target not found or invalid
41
+ def self.load(target_name)
42
+ # 1. Check plugin registry for engine-provided target profile
43
+ if RosettAi::Plugins::Registry.registered?(:engine, target_name)
44
+ plugin = RosettAi::Plugins::Registry.plugin_module(:engine, target_name)
45
+ if plugin.respond_to?(:target_profile_path) && plugin.target_profile_path.exist?
46
+ data = RosettAi::YamlLoader.load_file(plugin.target_profile_path)
47
+ validate!(data, plugin.target_profile_path)
48
+ return new(data)
49
+ end
50
+ end
51
+
52
+ # 2. Fallback: file-based lookup (core conf/ tree)
53
+ path = engine_target_path(target_name)
54
+ path = legacy_target_path(target_name) unless path.exist?
55
+ unless path.exist?
56
+ available_list = available
57
+ hint = if available_list.empty?
58
+ "Install an engine: apt install rosett-ai-engine-#{target_name.tr('_', '-')}"
59
+ else
60
+ available_list.join(', ')
61
+ end
62
+ raise RosettAi::CompileError,
63
+ "Unknown target: #{target_name}. Available: #{hint}"
64
+ end
65
+
66
+ data = RosettAi::YamlLoader.load_file(path)
67
+ validate!(data, path)
68
+ new(data)
69
+ end
70
+
71
+ # @return [Array<String>] sorted list of all available target names
72
+ # (from plugins, engine dir, and legacy targets dir)
73
+ def self.available
74
+ plugin_targets = RosettAi::Plugins::Registry.available(:engine)
75
+ engine_targets = Dir.glob(RosettAi.root.join('conf', 'engines', '*', 'target.yml')).map do |file|
76
+ File.basename(File.dirname(file))
77
+ end
78
+ legacy_targets = Dir.glob(legacy_targets_dir.join('*.yml')).map do |file|
79
+ File.basename(file, '.yml')
80
+ end
81
+ (plugin_targets + engine_targets + legacy_targets).uniq.sort
82
+ end
83
+
84
+ def self.engine_target_path(name)
85
+ RosettAi.root.join('conf', 'engines', name, 'target.yml')
86
+ end
87
+
88
+ def self.legacy_target_path(name)
89
+ legacy_targets_dir.join("#{name}.yml")
90
+ end
91
+
92
+ def self.legacy_targets_dir
93
+ RosettAi.root.join('conf', 'targets')
94
+ end
95
+
96
+ def self.validate!(data, path)
97
+ schema_path = RosettAi.root.join('conf', 'schemas', 'target_schema.json')
98
+ schema = JSON.parse(schema_path.read)
99
+ schemer = JSONSchemer.schema(schema)
100
+ errors = schemer.validate(data).to_a
101
+ return if errors.empty?
102
+
103
+ messages = errors.map do |err|
104
+ pointer = err['data_pointer'].empty? ? 'root' : err['data_pointer']
105
+ "#{pointer}: #{err['type']}"
106
+ end
107
+ raise RosettAi::CompileError, "Invalid target profile #{path}: #{messages.join(', ')}"
108
+ end
109
+ private_class_method :validate!
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,101 @@
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 Completion
8
+ # Extracts command metadata from the Thor CLI registry for completion
9
+ # script generation. This is the single source of truth for all
10
+ # shell completion generators.
11
+ #
12
+ # @author hugo
13
+ # @author claude
14
+ class Generator
15
+ SUPPORTED_SHELLS = ['bash', 'zsh', 'fish'].freeze
16
+
17
+ # @return [Hash] command tree extracted from Thor CLI
18
+ attr_reader :command_tree
19
+
20
+ def initialize
21
+ @command_tree = extract_command_tree
22
+ end
23
+
24
+ # Generate a completion script for the given shell.
25
+ #
26
+ # @param shell [String] one of 'bash', 'zsh', 'fish'
27
+ # @return [String] the completion script content
28
+ # @raise [RosettAi::Error] if shell is unsupported
29
+ def generate(shell)
30
+ shell = shell.to_s.downcase
31
+ unless SUPPORTED_SHELLS.include?(shell)
32
+ raise RosettAi::Error,
33
+ "Shell #{shell} is not supported — available: #{SUPPORTED_SHELLS.join(', ')}"
34
+ end
35
+
36
+ shell_generator_for(shell).generate
37
+ end
38
+
39
+ # @param shell [String] shell name
40
+ # @return [Boolean] whether the shell is supported
41
+ def supported?(shell)
42
+ SUPPORTED_SHELLS.include?(shell.to_s.downcase)
43
+ end
44
+
45
+ private
46
+
47
+ def shell_generator_for(shell)
48
+ case shell
49
+ when 'bash' then Shells::BashGenerator.new(command_tree)
50
+ when 'zsh' then Shells::ZshGenerator.new(command_tree)
51
+ when 'fish' then Shells::FishGenerator.new(command_tree)
52
+ end
53
+ end
54
+
55
+ def extract_command_tree
56
+ cli_class = RosettAi::Thor::CLI
57
+ tree = {}
58
+
59
+ cli_class.all_commands.each_value do |cmd|
60
+ next if cmd.name == 'help'
61
+
62
+ tree[cmd.name] = extract_command_info(cli_class, cmd)
63
+ end
64
+
65
+ tree
66
+ end
67
+
68
+ def extract_command_info(cli_class, cmd)
69
+ info = { description: cmd.description, options: extract_options(cmd) }
70
+ sub_class = find_subcommand_class(cli_class, cmd.name)
71
+ info[:subcommands] = extract_subcommands(sub_class) if sub_class
72
+ info
73
+ end
74
+
75
+ def extract_options(cmd)
76
+ return {} unless cmd.respond_to?(:options) && cmd.options
77
+
78
+ cmd.options.transform_values do |opt|
79
+ { type: opt.type.to_s, description: opt.description, enum: opt.enum }
80
+ end
81
+ end
82
+
83
+ def find_subcommand_class(cli_class, name)
84
+ cli_class.subcommand_classes[name]
85
+ end
86
+
87
+ def extract_subcommands(sub_class)
88
+ subs = {}
89
+ sub_class.all_commands.each_value do |cmd|
90
+ next if cmd.name == 'help'
91
+
92
+ subs[cmd.name] = {
93
+ description: cmd.description,
94
+ options: extract_options(cmd)
95
+ }
96
+ end
97
+ subs
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,126 @@
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 Completion
8
+ module Shells
9
+ # Generates bash completion scripts from the Thor command tree.
10
+ #
11
+ # Uses bash-completion framework conventions with _raictl() function.
12
+ # Dynamic completions for behaviour names and engine names use
13
+ # rai subcommands at tab-completion time.
14
+ #
15
+ # @author hugo
16
+ # @author claude
17
+ class BashGenerator
18
+ # @param command_tree [Hash] command tree from Generator
19
+ def initialize(command_tree)
20
+ @command_tree = command_tree
21
+ end
22
+
23
+ # @return [String] bash completion script
24
+ def generate
25
+ lines = []
26
+ lines << header
27
+ lines << main_function
28
+ lines << subcommand_functions
29
+ lines << dynamic_completions
30
+ lines << registration
31
+ lines.join("\n")
32
+ end
33
+
34
+ private
35
+
36
+ def header
37
+ <<~BASH
38
+ # bash completion for Rosett-AI — generated by rai completion bash
39
+ # Do not edit manually; regenerate with: rai completion bash
40
+ BASH
41
+ end
42
+
43
+ def main_function
44
+ commands = @command_tree.keys.sort
45
+ <<~BASH
46
+ _raictl() {
47
+ local cur prev words cword
48
+ _init_completion || return
49
+
50
+ local commands="#{shell_escape(commands.join(' '))}"
51
+
52
+ if [[ ${cword} -eq 1 ]]; then
53
+ COMPREPLY=( $(compgen -W "${commands}" -- "${cur}") )
54
+ return
55
+ fi
56
+
57
+ local subcmd="${words[1]}"
58
+ case "${subcmd}" in
59
+ #{subcommand_cases.chomp}
60
+ *)
61
+ COMPREPLY=()
62
+ ;;
63
+ esac
64
+ }
65
+ BASH
66
+ end
67
+
68
+ def subcommand_cases
69
+ lines = []
70
+ @command_tree.each do |name, info|
71
+ subs = info[:subcommands]
72
+ next unless subs
73
+
74
+ sub_names = subs.keys.sort.join(' ')
75
+ options = collect_options(subs)
76
+ words = [sub_names, options].reject(&:empty?).join(' ')
77
+ lines << " #{shell_escape(name)})\n"
78
+ lines << " COMPREPLY=( $(compgen -W \"#{shell_escape(words)}\" -- \"${cur}\") )\n"
79
+ lines << " ;;\n"
80
+ end
81
+ lines.join
82
+ end
83
+
84
+ def subcommand_functions
85
+ '' # Inline in case statement for simplicity
86
+ end
87
+
88
+ def dynamic_completions
89
+ <<~BASH
90
+
91
+ _rai_behaviour_names() {
92
+ local names
93
+ names=$(rai behaviour list --format=names 2>/dev/null)
94
+ COMPREPLY=( $(compgen -W "${names}" -- "${cur}") )
95
+ }
96
+
97
+ _rosett_ai_engine_names() {
98
+ local names
99
+ names=$(rai engines list --format=names 2>/dev/null)
100
+ COMPREPLY=( $(compgen -W "${names}" -- "${cur}") )
101
+ }
102
+ BASH
103
+ end
104
+
105
+ def registration
106
+ <<~BASH
107
+ complete -F _raictl rosett-ai
108
+ BASH
109
+ end
110
+
111
+ def collect_options(subcommands)
112
+ subcommands.each_value
113
+ .flat_map { |s| (s[:options] || {}).keys }
114
+ .uniq
115
+ .sort
116
+ .map { |o| "--#{o.to_s.tr('_', '-')}" }
117
+ .join(' ')
118
+ end
119
+
120
+ def shell_escape(str)
121
+ str.gsub(/[^a-zA-Z0-9_ .,-]/, '')
122
+ end
123
+ end
124
+ end
125
+ end
126
+ 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
+ module RosettAi
7
+ module Completion
8
+ module Shells
9
+ # Generates fish completion scripts from the Thor command tree.
10
+ #
11
+ # Uses fish's `complete` builtin with condition-based subcommand
12
+ # completion. Supports subcommand descriptions and dynamic
13
+ # completions for behaviour/engine names.
14
+ #
15
+ # @author hugo
16
+ # @author claude
17
+ class FishGenerator
18
+ # @param command_tree [Hash] command tree from Generator
19
+ def initialize(command_tree)
20
+ @command_tree = command_tree
21
+ end
22
+
23
+ # @return [String] fish completion script
24
+ def generate
25
+ lines = []
26
+ lines << header
27
+ lines << disable_file_completion
28
+ lines << top_level_completions
29
+ lines << subcommand_completions
30
+ lines.join("\n")
31
+ end
32
+
33
+ private
34
+
35
+ def header
36
+ <<~FISH
37
+ # fish completion for Rosett-AI — generated by rai completion fish
38
+ # Do not edit manually; regenerate with: rai completion fish
39
+ FISH
40
+ end
41
+
42
+ def disable_file_completion
43
+ "complete -c rosett-ai -f\n"
44
+ end
45
+
46
+ def top_level_completions
47
+ lines = []
48
+ @command_tree.each do |name, info|
49
+ desc = fish_escape(info[:description].to_s)
50
+ lines << "complete -c rosett-ai -n '__fish_use_subcommand' " \
51
+ "-a '#{fish_escape(name)}' -d '#{desc}'"
52
+ end
53
+ "#{lines.sort.join("\n")}\n"
54
+ end
55
+
56
+ def subcommand_completions
57
+ lines = []
58
+ @command_tree.each do |name, info|
59
+ subs = info[:subcommands]
60
+ next unless subs
61
+
62
+ subs.each do |sub_name, sub_info|
63
+ desc = fish_escape(sub_info[:description].to_s)
64
+ lines << 'complete -c rosett-ai ' \
65
+ "-n '__fish_seen_subcommand_from #{fish_escape(name)}' " \
66
+ "-a '#{fish_escape(sub_name)}' -d '#{desc}'"
67
+ end
68
+ end
69
+ "#{lines.sort.join("\n")}\n"
70
+ end
71
+
72
+ def fish_escape(str)
73
+ str.gsub("'", "\\\\'")
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end