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,219 @@
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 'terminal-table'
7
+
8
+ module RosettAi
9
+ module Thor
10
+ module Tasks
11
+ # Thor CLI for git hook management.
12
+ #
13
+ # Provides install, uninstall, list, and status subcommands
14
+ # for managing Rosett-AI-integrated git hooks.
15
+ #
16
+ # @author hugo
17
+ # @author claude
18
+ class Hooks < ::Thor
19
+ desc 'install', 'Install configured git hooks from .rosett-ai/config.yml'
20
+ long_desc <<~LONGDESC
21
+ Install git hooks defined in .rosett-ai/config.yml to the project's
22
+ .git/hooks/ directory. Detects existing hook managers (overcommit,
23
+ husky, lefthook) and chains with them when possible.
24
+
25
+ Hooks are defined as shell command arrays to prevent injection.
26
+ Use --verbose to see detected hook managers.
27
+
28
+ Exit codes: 0 success, 1 install errors, 3 no config.
29
+
30
+ EXAMPLES
31
+
32
+ raictl hooks install
33
+ raictl hooks install --verbose
34
+
35
+ Related: hooks uninstall, hooks status
36
+ LONGDESC
37
+ method_option :verbose, type: :boolean, default: false, desc: 'Show detailed output'
38
+ def install
39
+ config = load_hooks_config
40
+ return exit_with_error(t('no_config')) if config.empty?
41
+
42
+ installer = RosettAi::GitHooks::Installer.new(
43
+ project_root: project_root,
44
+ hooks_config: config
45
+ )
46
+
47
+ detect_existing_hooks(installer) if options[:verbose]
48
+ results = installer.install
49
+ render_install_results(results)
50
+ exit(results[:errors].empty? ? 0 : 1)
51
+ end
52
+
53
+ desc 'uninstall', 'Remove rai hooks and restore previous hooks'
54
+ long_desc <<~LONGDESC
55
+ Remove all Rosett-AI-installed hooks and restore any previously backed-up
56
+ hooks. Does not affect hooks installed by other managers.
57
+
58
+ EXAMPLES
59
+
60
+ raictl hooks uninstall
61
+
62
+ Related: hooks install, hooks status
63
+ LONGDESC
64
+ def uninstall
65
+ installer = build_installer({})
66
+ results = installer.uninstall
67
+ render_uninstall_results(results)
68
+ exit(results[:errors].empty? ? 0 : 1)
69
+ end
70
+
71
+ desc 'list', 'Show installed rai hooks'
72
+ long_desc <<~LONGDESC
73
+ List all currently installed rai hooks with their configured
74
+ commands. Use --format json for machine-readable output.
75
+
76
+ EXAMPLES
77
+
78
+ raictl hooks list
79
+ raictl hooks list --format json
80
+
81
+ Related: hooks status, hooks install
82
+ LONGDESC
83
+ method_option :format, type: :string, default: 'table', enum: ['table', 'json'], desc: 'Output format'
84
+ def list
85
+ installer = build_installer({})
86
+ hooks = installer.list
87
+
88
+ if hooks.empty?
89
+ say(t('no_hooks_installed'))
90
+ return
91
+ end
92
+
93
+ render_list(hooks)
94
+ end
95
+
96
+ desc 'status', 'Show hook installation status and detect drift'
97
+ long_desc <<~LONGDESC
98
+ Compare installed hooks against .rosett-ai/config.yml and report drift.
99
+
100
+ Shows four categories: in sync, missing (configured but not
101
+ installed), drifted (installed but modified), and extra (installed
102
+ but not configured).
103
+
104
+ Exit codes: 0 all in sync, 1 drift detected.
105
+
106
+ EXAMPLES
107
+
108
+ raictl hooks status
109
+ raictl hooks status --format json
110
+
111
+ Related: hooks install, hooks list
112
+ LONGDESC
113
+ method_option :format, type: :string, default: 'table', enum: ['table', 'json'], desc: 'Output format'
114
+ def status
115
+ config = load_hooks_config
116
+ installer = RosettAi::GitHooks::Installer.new(
117
+ project_root: project_root,
118
+ hooks_config: config
119
+ )
120
+
121
+ result = installer.status
122
+ render_status(result)
123
+
124
+ all_synced = result[:missing].empty? && result[:drifted].empty? && result[:extra].empty?
125
+ exit(all_synced ? 0 : 1)
126
+ end
127
+
128
+ private
129
+
130
+ def t(key, **args)
131
+ ::I18n.t("rosett_ai.hooks.#{key}", **args)
132
+ end
133
+
134
+ def project_root
135
+ Pathname.new(ENV.fetch('RAI_ORIGINAL_PWD', Dir.pwd))
136
+ end
137
+
138
+ def load_hooks_config
139
+ config_path = project_root.join('.rosett-ai', 'config.yml')
140
+ return {} unless config_path.exist?
141
+
142
+ data = YAML.safe_load_file(config_path, permitted_classes: [Symbol]) || {}
143
+ data.fetch('hooks', {})
144
+ end
145
+
146
+ def build_installer(config)
147
+ RosettAi::GitHooks::Installer.new(
148
+ project_root: project_root,
149
+ hooks_config: config
150
+ )
151
+ end
152
+
153
+ def detect_existing_hooks(_installer)
154
+ detector = RosettAi::GitHooks::ChainDetector.new(project_root: project_root)
155
+ managers = detector.detect
156
+
157
+ return if managers.empty?
158
+
159
+ say(t('detected_managers', managers: managers.map { |m| m[:name] }.join(', ')))
160
+ end
161
+
162
+ def exit_with_error(message)
163
+ warn(Rainbow(message).red)
164
+ exit(3)
165
+ end
166
+
167
+ def render_install_results(results)
168
+ results[:installed].each { |h| say(Rainbow(" installed #{h}").green) }
169
+ results[:skipped].each { |h| say(Rainbow(" skipped #{h}").yellow) }
170
+ results[:errors].each { |e| warn(Rainbow(" error: #{e}").red) }
171
+
172
+ total = results[:installed].size
173
+ say(t('install_summary', count: total)) if total.positive?
174
+ end
175
+
176
+ def render_uninstall_results(results)
177
+ results[:removed].each { |h| say(Rainbow(" removed #{h}").green) }
178
+ results[:restored].each { |h| say(Rainbow(" restored #{h}").cyan) }
179
+ results[:errors].each { |e| warn(Rainbow(" error: #{e}").red) }
180
+
181
+ total = results[:removed].size
182
+ say(t('uninstall_summary', count: total)) if total.positive?
183
+ end
184
+
185
+ def render_list(hooks)
186
+ if options[:format] == 'json'
187
+ say(JSON.pretty_generate(hooks))
188
+ return
189
+ end
190
+
191
+ table = ::Terminal::Table.new(
192
+ headings: ['Hook', 'Commands'],
193
+ rows: hooks.map { |h| [h[:name], h[:commands].join(', ')] }
194
+ )
195
+ say(table)
196
+ end
197
+
198
+ def render_status(result)
199
+ if options[:format] == 'json'
200
+ say(JSON.pretty_generate(result))
201
+ return
202
+ end
203
+
204
+ render_status_section('In sync', result[:in_sync], :green)
205
+ render_status_section('Missing', result[:missing], :yellow)
206
+ render_status_section('Drifted', result[:drifted], :red)
207
+ render_status_section('Extra', result[:extra], :cyan)
208
+ end
209
+
210
+ def render_status_section(label, hooks, color)
211
+ return if hooks.empty?
212
+
213
+ say("#{label}:")
214
+ hooks.each { |h| say(Rainbow(" #{h}").color(color)) }
215
+ end
216
+ end
217
+ end
218
+ end
219
+ end
@@ -0,0 +1,259 @@
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 'thor'
7
+ require 'terminal-table'
8
+ require 'rainbow'
9
+ require 'tty-spinner'
10
+
11
+ module RosettAi
12
+ module Thor
13
+ module Tasks
14
+ # Thor task for initializing rai project structure
15
+ class Init < ::Thor
16
+ default_task :setup
17
+
18
+ PROJECT_DIRS = ['conf', 'conf/behaviour', 'conf/design'].freeze
19
+
20
+ desc 'setup', 'Initialize rai project structure'
21
+ long_desc <<~LONGDESC
22
+ Initializes Rosett-AI directory structures and configuration files.
23
+ Can set up global (~/.claude/), local (.claude/), or project (.rosett-ai/) structures.
24
+
25
+ Flags:
26
+ --global: Set up global ~/.claude/ structure
27
+ --local: Set up project-local .claude/ structure
28
+ --project: Set up rai project structure (.rosett-ai/, config.yml)
29
+ --no-compile: Skip the compile step after initialization
30
+
31
+ At least one of --global, --local, or --project must be specified.
32
+ Automatically runs compile unless --no-compile is used.
33
+
34
+ EXAMPLES
35
+
36
+ raictl init --global
37
+ raictl init --global --local
38
+ raictl init --project
39
+ raictl init --global --no-compile
40
+ LONGDESC
41
+ method_option :global,
42
+ type: :boolean,
43
+ default: false,
44
+ desc: 'Set up global ~/.claude/ structure'
45
+ method_option :local,
46
+ type: :boolean,
47
+ default: false,
48
+ desc: 'Set up project-local .claude/ structure'
49
+ method_option :project,
50
+ type: :boolean,
51
+ default: false,
52
+ desc: 'Set up rai project structure (.rosett-ai/, config.yml)'
53
+ method_option :no_compile,
54
+ type: :boolean,
55
+ default: false,
56
+ desc: 'Skip the compile step'
57
+
58
+ def setup
59
+ raise ::Thor::Error, t('no_scope') unless options[:global] || options[:local] || options[:project]
60
+
61
+ results = { created: 0, ok: 0, skipped: 0 }
62
+ setup_global(results) if options[:global]
63
+ setup_project(results) if options[:project]
64
+ setup_local(results) if options[:local]
65
+ run_compile unless options[:no_compile]
66
+ print_summary(results)
67
+ end
68
+
69
+ private
70
+
71
+ def t(key, **)
72
+ ::I18n.t("rosett_ai.cli.init.#{key}", **)
73
+ end
74
+
75
+ def setup_global(results)
76
+ results[:global] = run_with_spinner(t('global_init')) do
77
+ base_dir = RosettAi.paths.global_dir.to_s
78
+ {
79
+ dirs: RosettAi::Init::DirectoryBuilder.create_global_structure(base_dir),
80
+ files: RosettAi::Init::FileCopier.copy_global_assets(base_dir)
81
+ }
82
+ end
83
+
84
+ config_result = RosettAi::Init::ConfigFileWriter.write_default
85
+ log_config_result(config_result)
86
+ register_mcp_server
87
+ run_engine_detection if RosettAi.rai_config.detect_on_init?
88
+ end
89
+
90
+ def setup_local(results)
91
+ results[:local] = run_with_spinner(t('local_init')) do
92
+ base_dir = RosettAi.paths.local_dir.to_s
93
+ {
94
+ dirs: RosettAi::Init::DirectoryBuilder.create_local_structure(base_dir),
95
+ files: RosettAi::Init::FileCopier.copy_local_assets(base_dir)
96
+ }
97
+ end
98
+ end
99
+
100
+ def setup_project(results)
101
+ project_dir = Pathname.new(RosettAi.paths.user_pwd).join('.rosett-ai')
102
+
103
+ if project_dir.directory?
104
+ puts Rainbow(t('project_exists', path: project_dir)).yellow
105
+ return
106
+ end
107
+
108
+ results[:project] = run_with_spinner(t('project_init')) do
109
+ create_project_structure(project_dir)
110
+ end
111
+ end
112
+
113
+ def create_project_structure(project_dir)
114
+ dirs = create_project_dirs(project_dir)
115
+ files = create_project_config(project_dir)
116
+ { dirs: dirs, files: files }
117
+ end
118
+
119
+ def create_project_dirs(project_dir)
120
+ all_dirs = [project_dir] + PROJECT_DIRS.map { |subdir| project_dir.join(subdir) }
121
+ all_dirs.reject(&:directory?).each { |dir| FileUtils.mkdir_p(dir) }.map(&:to_s)
122
+ end
123
+
124
+ def create_project_config(project_dir)
125
+ path = project_dir.join('config.yml')
126
+ return [] if path.exist?
127
+
128
+ write_project_config(path)
129
+ [path.to_s]
130
+ end
131
+
132
+ def write_project_config(path)
133
+ project_name = File.basename(RosettAi.paths.user_pwd)
134
+ content = {
135
+ 'project_name' => project_name,
136
+ 'default_engine' => 'claude'
137
+ }
138
+ File.open(path, 'w', 0o644) { |f| f.write(content.to_yaml) }
139
+ end
140
+
141
+ def run_with_spinner(message)
142
+ unless RosettAi::Ui::TtyHelper.interactive?
143
+ warn(message)
144
+ return yield
145
+ end
146
+
147
+ spinner = TTY::Spinner.new("[:spinner] #{message}", format: :dots, clear: true)
148
+ spinner.auto_spin
149
+ result = yield
150
+ spinner.success(Rainbow(t('done')).green)
151
+ result
152
+ rescue StandardError => e
153
+ spinner&.error(Rainbow(t('failed')).red)
154
+ raise RosettAi::InitError, t('init_failed', message: e.message)
155
+ end
156
+
157
+ def run_compile
158
+ unless RosettAi::Ui::TtyHelper.interactive?
159
+ warn(t('compiling'))
160
+ require_relative 'compile'
161
+ Compile.new.invoke(:generate)
162
+ return
163
+ end
164
+
165
+ spinner = TTY::Spinner.new(
166
+ "[:spinner] #{t('compiling')}",
167
+ format: :dots,
168
+ clear: true
169
+ )
170
+ spinner.auto_spin
171
+
172
+ require_relative 'compile'
173
+ Compile.new.invoke(:generate)
174
+ rescue RosettAi::CompileError => e
175
+ spinner&.error(Rainbow(t('done')).yellow)
176
+ puts Rainbow(" #{e.message}").yellow
177
+ puts Rainbow(" #{t('compile_skipped_hint')}").yellow
178
+ end
179
+
180
+ def print_summary(results)
181
+ return unless results[:global] || results[:local] || results[:project]
182
+
183
+ puts
184
+ table = ::Terminal::Table.new(
185
+ title: Rainbow(t('summary_title')).bright,
186
+ headings: [t('item'), t('count')],
187
+ rows: build_summary_rows(results),
188
+ style: { border: :unicode_round }
189
+ )
190
+ puts table
191
+ end
192
+
193
+ def build_summary_rows(results)
194
+ rows = []
195
+ add_result_rows(rows, results[:global], 'Global') if results[:global]
196
+ add_result_rows(rows, results[:project], 'Project') if results[:project]
197
+ add_result_rows(rows, results[:local], 'Local') if results[:local]
198
+ rows
199
+ end
200
+
201
+ def add_result_rows(rows, result, prefix)
202
+ rows << [t('dirs_created', prefix: prefix), result[:dirs].size]
203
+ rows << [t('files_copied', prefix: prefix), result[:files].size]
204
+ end
205
+
206
+ def log_config_result(result)
207
+ case result[:action]
208
+ when :created
209
+ puts " #{Rainbow(t('config_created', path: result[:path])).green}"
210
+ when :unchanged
211
+ puts " #{Rainbow(t('config_exists', path: result[:path])).cyan}"
212
+ end
213
+ end
214
+
215
+ def register_mcp_server
216
+ registrar = RosettAi::Init::McpRegistrar.new
217
+ result = registrar.register
218
+ log_mcp_result(result)
219
+ end
220
+
221
+ def log_mcp_result(result)
222
+ case result[:action]
223
+ when :registered
224
+ puts " #{Rainbow(t('mcp_registered', path: result[:path])).green}"
225
+ when :already_registered
226
+ puts " #{Rainbow(t('mcp_already_registered')).cyan}"
227
+ when :skipped
228
+ puts " #{Rainbow(t('mcp_skipped', reason: result[:reason])).yellow}"
229
+ end
230
+ end
231
+
232
+ def run_engine_detection
233
+ puts
234
+ puts Rainbow(t('detected_engines_title')).bright
235
+ RosettAi::Engines::Registry.available.each do |name|
236
+ manifest = RosettAi::Engines::Registry.manifest(name)
237
+ next unless manifest.dig('components', 'detector')
238
+
239
+ detector = resolve_detector(name, manifest)
240
+ result = detector.detect
241
+ status = result[:detected] ? Rainbow(t('detected')).green : Rainbow(t('not_detected')).yellow
242
+ puts " #{result[:display_name]}: #{status}"
243
+ end
244
+ puts " #{t('edit_config_hint', path: RosettAi.paths.rai_config_dir.join('config.yml'))}"
245
+ end
246
+
247
+ def resolve_detector(name, manifest)
248
+ engine_mod = RosettAi::Engines::Registry.engine_module(name)
249
+ detector_klass = engine_mod.detector_class
250
+ if manifest.dig('components', 'detector') && detector_klass
251
+ detector_klass.new(name)
252
+ else
253
+ RosettAi::Engines::Detector.new(name)
254
+ end
255
+ end
256
+ end
257
+ end
258
+ end
259
+ end
@@ -0,0 +1,120 @@
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 'thor'
7
+ require 'rainbow'
8
+ require 'terminal-table'
9
+
10
+ module RosettAi
11
+ module Thor
12
+ module Tasks
13
+ # Thor task for license key management.
14
+ #
15
+ # License keys control access to premium content pack downloads —
16
+ # they NEVER restrict software functionality (GPL-3.0 compliance).
17
+ class License < ::Thor
18
+ desc 'activate KEY', 'Activate a license key'
19
+ long_desc <<~LONGDESC
20
+ Activates a premium license key for content pack downloads.
21
+ Validates and stores the license key for future use. License keys control access to
22
+ premium content only — they never restrict software functionality (GPL-3.0 compliance).
23
+
24
+ Exit codes:
25
+ 0 - Success
26
+ 1 - Invalid key or validation failure
27
+
28
+ EXAMPLES
29
+
30
+ raictl license activate NNCC-eyJhbGciOi...
31
+
32
+ Related commands: status, deactivate
33
+ LONGDESC
34
+ def activate(raw_key)
35
+ key = RosettAi::Licensing::LicenseKey.new(raw_key)
36
+ key.decode
37
+
38
+ store = RosettAi::Licensing::LicenseStore.new
39
+ store.save(raw_key)
40
+
41
+ puts Rainbow(t('activate.success', tier: key.tier.to_s)).green
42
+ rescue RosettAi::LicenseError => e
43
+ raise ::Thor::Error, t('activate.invalid', reason: e.message)
44
+ end
45
+
46
+ desc 'status', 'Show current license status'
47
+ long_desc <<~LONGDESC
48
+ Displays the current license tier (community or premium) and validation status.
49
+ Shows any warnings if the license key has issues.
50
+
51
+ EXAMPLES
52
+
53
+ raictl license status
54
+
55
+ Related commands: activate, deactivate
56
+ LONGDESC
57
+ def status
58
+ result = RosettAi::Licensing::LicenseValidator.new.validate
59
+
60
+ if result[:tier].name == :community
61
+ puts Rainbow(t('status.community')).yellow
62
+ return
63
+ end
64
+
65
+ print_status_table(result)
66
+ print_warnings(result[:warnings])
67
+ end
68
+
69
+ desc 'deactivate', 'Remove license key and revert to community tier'
70
+ long_desc <<~LONGDESC
71
+ Removes the stored license key and reverts to the community tier.
72
+ Does not affect already downloaded content packs.
73
+
74
+ EXAMPLES
75
+
76
+ raictl license deactivate
77
+
78
+ Related commands: activate, status
79
+ LONGDESC
80
+ def deactivate
81
+ store = RosettAi::Licensing::LicenseStore.new
82
+
83
+ unless store.exists?
84
+ puts Rainbow(t('deactivate.not_found')).yellow
85
+ return
86
+ end
87
+
88
+ store.remove!
89
+ puts Rainbow(t('deactivate.success')).green
90
+ end
91
+
92
+ private
93
+
94
+ def t(key, **)
95
+ ::I18n.t("rosett_ai.cli.license.#{key}", **)
96
+ end
97
+
98
+ def print_status_table(result)
99
+ rows = [
100
+ [t('status.tier_label'), result[:tier].to_s],
101
+ [t('status.valid_label'), result[:valid] ? Rainbow('Yes').green : Rainbow('No').red]
102
+ ]
103
+
104
+ table = ::Terminal::Table.new(
105
+ title: Rainbow(t('status.title')).bright,
106
+ rows: rows,
107
+ style: { border: :unicode_round }
108
+ )
109
+ puts table
110
+ end
111
+
112
+ def print_warnings(warnings)
113
+ warnings.each do |warning|
114
+ puts Rainbow(" #{t('status.warning_prefix')}: #{warning}").yellow
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end