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,269 @@
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
+
9
+ module RosettAi
10
+ module Thor
11
+ # Main CLI entry point for Rosett-AI commands
12
+ class CLI < ::Thor
13
+ INTERNAL_COMMANDS = ['build', 'release', 'documentation', 'dbus', 'desktop'].freeze
14
+
15
+ def self.exit_on_failure?
16
+ true
17
+ end
18
+
19
+ # rubocop:disable Style/OptionalBooleanParameter -- matches Thor's API
20
+ def self.printable_commands(all = true, subcommand = false)
21
+ # rubocop:enable Style/OptionalBooleanParameter
22
+ cmds = super
23
+ return cmds if RosettAi.context.rai_internal?
24
+
25
+ cmds.reject do |row|
26
+ words = row.first.split
27
+ INTERNAL_COMMANDS.include?(words[1])
28
+ end
29
+ end
30
+
31
+ package_name 'raictl'
32
+
33
+ class_option :accessible, type: :boolean, default: false,
34
+ desc: 'Enable accessible output mode'
35
+ class_option :locale, type: :string, default: nil,
36
+ desc: 'Display output in specified locale'
37
+ class_option :ui, type: :string, default: nil,
38
+ enum: ['tui', 'gtk4', 'qt6', 'kde'],
39
+ desc: 'UI adapter to use'
40
+
41
+ map ['-v', '--version'] => :version
42
+
43
+ no_commands do # rubocop:disable Metrics/BlockLength -- telemetry helpers extend invoke_command
44
+ def invoke_command(command, *args)
45
+ cmd_name = command.name
46
+ if INTERNAL_COMMANDS.include?(cmd_name) && !RosettAi.context.rai_internal?
47
+ raise ::Thor::Error, ::I18n.t('rosett_ai.cli.internal_only', command: cmd_name)
48
+ end
49
+
50
+ RosettAi.setup_i18n(locale: options[:locale]) if options[:locale]
51
+ telemetry = start_telemetry(cmd_name)
52
+ super
53
+ finish_telemetry(telemetry, 'success')
54
+ rescue Errno::EACCES, Errno::ENOENT => e
55
+ handle_system_error(telemetry, e)
56
+ rescue RosettAi::Error => e
57
+ finish_telemetry(telemetry, 'error', error_class: e.class.name)
58
+ exit_code = RosettAi::ErrorHandler.handle(stderr: $stderr) { raise e }
59
+ exit exit_code
60
+ end
61
+
62
+ def handle_system_error(telemetry, error)
63
+ finish_telemetry(telemetry, 'error', error_class: error.class.name)
64
+ if error.is_a?(Errno::EACCES)
65
+ path = error.message.sub(/^Permission denied @ .+ - /, '')
66
+ warn Rainbow(::I18n.t('rosett_ai.errors.permission_denied', path: path)).red
67
+ exit RosettAi::ExitCodes::PERMISSION
68
+ else
69
+ warn Rainbow(error.message).red
70
+ exit RosettAi::ExitCodes::GENERAL
71
+ end
72
+ end
73
+
74
+ def start_telemetry(command_name)
75
+ return unless RosettAi::Telemetry.enabled?
76
+
77
+ RosettAi::Telemetry.reporter.command_start(command: command_name)
78
+ { command: command_name, start: Process.clock_gettime(Process::CLOCK_MONOTONIC) }
79
+ end
80
+
81
+ def finish_telemetry(context, status, **extra)
82
+ return unless context
83
+
84
+ duration = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - context[:start]) * 1000
85
+ RosettAi::Telemetry.reporter.command_end(
86
+ command: context[:command], duration_ms: duration.round(1), status: status, **extra
87
+ )
88
+ end
89
+ end
90
+
91
+ desc 'version', 'Display rosett-ai version'
92
+ def version
93
+ puts ::I18n.t('rosett_ai.cli.version', version: RosettAi::VERSION)
94
+ build_info = RosettAi.root.join('..', 'etc', 'BUILD_INFO')
95
+ return unless build_info.exist?
96
+
97
+ info = parse_build_info(build_info)
98
+ puts " commit: #{info['commit']}" if info['commit']
99
+ puts " branch: #{info['branch']}" if info['branch']
100
+ puts " built: #{info['build_date']}" if info['build_date']
101
+ puts " variant: #{info['variant']}" if info['variant']
102
+ end
103
+
104
+ no_commands do
105
+ def parse_build_info(path)
106
+ path.read.each_line.with_object({}) do |line, hash|
107
+ key, value = line.strip.split('=', 2)
108
+ hash[key] = value if key && value
109
+ end
110
+ end
111
+ end
112
+
113
+ desc 'documentation SUBCOMMAND', 'Manage and generate project documentation'
114
+ subcommand 'documentation', RosettAi::Thor::Tasks::Documentation
115
+
116
+ desc 'behaviour SUBCOMMAND', 'Manage behaviour configuration files'
117
+ subcommand 'behaviour', RosettAi::Thor::Tasks::Behaviour
118
+
119
+ desc 'compile', 'Compile YAML configurations into markdown rule files'
120
+ subcommand 'compile', RosettAi::Thor::Tasks::Compile
121
+
122
+ desc 'adopt', 'Analyze compiled rule files for issues'
123
+ subcommand 'adopt', RosettAi::Thor::Tasks::Adopt
124
+
125
+ desc 'init', 'Initialize rosett-ai directory structure'
126
+ subcommand 'init', RosettAi::Thor::Tasks::Init
127
+
128
+ desc 'backup', 'Back up rosett-ai directory structure and assets'
129
+ subcommand 'backup', RosettAi::Thor::Tasks::Backup
130
+
131
+ desc 'build SUBCOMMAND', 'Build .deb packages for core and engine plugins'
132
+ subcommand 'build', RosettAi::Thor::Tasks::Build
133
+
134
+ desc 'validate', 'Validate all configuration files (behaviour, design, tooling)'
135
+ subcommand 'validate', RosettAi::Thor::Tasks::Validate
136
+
137
+ desc 'doctor', 'Run diagnostic checks on rosett-ai environment'
138
+ subcommand 'doctor', RosettAi::Thor::Tasks::Doctor
139
+
140
+ desc 'migrate', 'Migrate legacy .nncc and ~/.config/nncc paths'
141
+ subcommand 'migrate', RosettAi::Thor::Tasks::Migrate
142
+
143
+ desc 'design SUBCOMMAND', 'Manage design documents and schemas'
144
+ subcommand 'design', RosettAi::Thor::Tasks::Design
145
+
146
+ desc 'config SUBCOMMAND', 'Compile and inspect AI tool configurations'
147
+ subcommand 'config', RosettAi::Thor::Tasks::Config
148
+
149
+ desc 'tooling SUBCOMMAND', 'Validate CI/CD files and check version consistency'
150
+ subcommand 'tooling', RosettAi::Thor::Tasks::Tooling
151
+
152
+ desc 'license SUBCOMMAND', 'Manage license keys (activate, status, deactivate)'
153
+ subcommand 'license', RosettAi::Thor::Tasks::License
154
+
155
+ desc 'content SUBCOMMAND', 'Install and manage premium content packs'
156
+ subcommand 'content', RosettAi::Thor::Tasks::Content
157
+
158
+ desc 'release SUBCOMMAND', 'Prepare, tag, and track releases'
159
+ subcommand 'release', RosettAi::Thor::Tasks::Release
160
+
161
+ desc 'engines SUBCOMMAND', 'Detect and manage AI engine plugins'
162
+ subcommand 'engines', RosettAi::Thor::Tasks::Engines
163
+
164
+ desc 'provenance SUBCOMMAND', 'Track AI contributions and authorship'
165
+ subcommand 'provenance', RosettAi::Thor::Tasks::Provenance
166
+
167
+ desc 'comply', 'Run compliance checks (CRA, license, SPDX headers)'
168
+ subcommand 'comply', RosettAi::Thor::Tasks::Comply
169
+
170
+ desc 'workflow SUBCOMMAND', 'Run and manage declarative workflows'
171
+ subcommand 'workflow', RosettAi::Thor::Tasks::Workflow
172
+
173
+ desc 'project SUBCOMMAND', 'Inspect and manage project lifecycle'
174
+ subcommand 'project', RosettAi::Thor::Tasks::Project
175
+
176
+ desc 'retrofit SUBCOMMAND', 'Reverse-compile native configs to Rosett-AI YAML'
177
+ subcommand 'retrofit', RosettAi::Thor::Tasks::Retrofit
178
+
179
+ desc 'hooks SUBCOMMAND', 'Install and manage git hooks'
180
+ subcommand 'hooks', RosettAi::Thor::Tasks::Hooks
181
+
182
+ desc 'mcp SUBCOMMAND', 'Manage MCP servers and run security audits'
183
+ subcommand 'mcp', RosettAi::Thor::Tasks::Mcp
184
+
185
+ desc 'completion SUBCOMMAND', 'Generate shell completion scripts'
186
+ subcommand 'completion', RosettAi::Thor::Tasks::Completion
187
+
188
+ desc 'dbus SUBCOMMAND', 'Start and monitor the D-Bus session service'
189
+ subcommand 'dbus', RosettAi::Thor::Tasks::DBus
190
+
191
+ desc 'desktop SUBCOMMAND', 'Launch desktop GUI applications'
192
+ subcommand 'desktop', RosettAi::Thor::Tasks::Desktop
193
+
194
+ desc 'plugins SUBCOMMAND', 'List, install, and remove plugins'
195
+ subcommand 'plugins', RosettAi::Thor::Tasks::Plugins
196
+
197
+ desc 'tree', 'Print a tree of all available commands'
198
+ def tree
199
+ say 'rai', :blue
200
+ leaves, groups = tree_partition_entries(self.class)
201
+ tree_render_entries(leaves, groups, ' ')
202
+ end
203
+
204
+ RosettAi::SmartFeedback::ThorMiddleware.install(self)
205
+
206
+ private
207
+
208
+ # Partition a Thor class's entries into leaf commands and expandable groups.
209
+ def tree_partition_entries(klass)
210
+ groups = tree_visible_subcommands(klass).sort_by(&:first)
211
+ group_set = groups.to_set(&:first)
212
+ leaves = tree_leaf_commands(klass, group_set).sort_by(&:first)
213
+ [leaves, groups]
214
+ end
215
+
216
+ # Returns leaf commands: visible commands that aren't also subcommand groups,
217
+ # plus subcommand aliases that have no visible children (compile, adopt, etc.).
218
+ def tree_leaf_commands(klass, group_set)
219
+ all_subcmd_names = klass.subcommand_classes.keys
220
+ visible = tree_visible_commands(klass).except(*all_subcmd_names)
221
+ subcmd_aliases = all_subcmd_names
222
+ .reject { |name| group_set.include?(name) }
223
+ .select { |name| klass.commands.key?(name) }
224
+ .map { |name| [name, klass.commands[name]] }
225
+ visible.to_a + subcmd_aliases
226
+ end
227
+
228
+ def tree_render_entries(leaves, groups, indent)
229
+ total = leaves.size + groups.size
230
+ leaves.each_with_index { |(name, cmd), i| tree_print_leaf(name, cmd, indent, i + 1 == total) }
231
+ groups.each_with_index do |(name, subclass), i|
232
+ tree_print_group(name, subclass, indent, leaves.size + i + 1 == total)
233
+ end
234
+ end
235
+
236
+ def tree_print_leaf(name, cmd, indent, last)
237
+ icon = last ? "\u2514\u2500" : "\u251C\u2500"
238
+ desc = cmd.description.split("\n").first || ''
239
+ say "#{indent}#{icon} ", nil, false
240
+ say name, :green, false
241
+ say " (#{desc})" unless desc.empty?
242
+ end
243
+
244
+ def tree_print_group(name, klass, indent, last)
245
+ icon = last ? "\u2514\u2500" : "\u251C\u2500"
246
+ pipe = last ? ' ' : "\u2502 "
247
+ say "#{indent}#{icon} ", nil, false
248
+ say name, :cyan
249
+ leaves, children = tree_partition_entries(klass)
250
+ tree_render_entries(leaves, children, "#{indent}#{pipe}")
251
+ end
252
+
253
+ def tree_visible_commands(klass)
254
+ default = klass.respond_to?(:default_task) ? klass.default_task.to_s : nil
255
+ hidden = ['help', 'tree']
256
+ filtered = klass.commands.reject { |name, cmd| cmd.hidden? || hidden.include?(name) || name == default }
257
+ return filtered if RosettAi.context.rai_internal?
258
+
259
+ filtered.except(*INTERNAL_COMMANDS)
260
+ end
261
+
262
+ def tree_visible_subcommands(klass)
263
+ klass.subcommand_classes.select do |_, subclass|
264
+ !tree_visible_commands(subclass).empty? || !tree_visible_subcommands(subclass).empty?
265
+ end
266
+ end
267
+ end
268
+ end
269
+ end
@@ -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 '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 analyzing compiled rule files using the Claude API
15
+ class Adopt < ::Thor
16
+ default_task :analyze
17
+
18
+ desc 'analyze', 'Analyze compiled rule files for issues'
19
+ long_desc <<~LONGDESC
20
+ Analyzes compiled rule files for structural issues, anti-patterns, and policy violations.
21
+ By default, runs local-only structural checks. Use --api to enable remote AI analysis.
22
+
23
+ Flags:
24
+ --elaborate: Show detailed explanation column in findings table
25
+ --verbose: Show processing details and sensitive file list
26
+ --api: Enable remote API analysis (requires engine with executor)
27
+ --quorum: Run analysis across all available engines (requires --api)
28
+
29
+ EXAMPLES
30
+
31
+ raictl adopt
32
+ raictl adopt --verbose --elaborate
33
+ raictl adopt --api
34
+ raictl adopt --api --quorum
35
+
36
+ Related commands: raictl compile, rai behaviour validate
37
+ LONGDESC
38
+ method_option :elaborate,
39
+ type: :boolean,
40
+ default: false,
41
+ desc: 'Show detailed explanation column'
42
+ method_option :verbose,
43
+ type: :boolean,
44
+ default: false,
45
+ desc: 'Show processing details'
46
+ method_option :local,
47
+ type: :boolean,
48
+ default: true,
49
+ desc: 'Local-only structural checks, no API calls (default)'
50
+ method_option :api,
51
+ type: :boolean,
52
+ default: false,
53
+ desc: 'Enable remote API analysis (requires engine with executor)'
54
+ method_option :quorum,
55
+ type: :boolean,
56
+ default: false,
57
+ desc: 'Run analysis across all available engines (requires --api)'
58
+
59
+ def analyze
60
+ adopter = build_adopter
61
+ files = adopter.discover_managed_files
62
+
63
+ if files.empty?
64
+ puts Rainbow(t('no_files')).yellow
65
+ return
66
+ end
67
+
68
+ log_verbose(t('found_files', count: files.size))
69
+
70
+ sensitive = adopter.sensitive_files
71
+ log_verbose(t('sensitive_skipped', count: sensitive.size)) unless sensitive.empty?
72
+ if options[:verbose] && !sensitive.empty?
73
+ sensitive.each { |f| puts " #{Rainbow(File.basename(f)).yellow} #{t('sensitive_label')}" }
74
+ end
75
+
76
+ outcome = run_analysis(adopter)
77
+ print_results(outcome)
78
+ end
79
+
80
+ private
81
+
82
+ def t(key, **)
83
+ ::I18n.t("rosett_ai.cli.adopt.#{key}", **)
84
+ end
85
+
86
+ def build_adopter
87
+ xdg_conf = RosettAi.paths.rai_conf_dir
88
+ RosettAi::Adopter::RuleAdopter.new(
89
+ rules_dir: rules_dir,
90
+ cache_path: xdg_conf.join('adopt.cache.yml'),
91
+ redactions_path: xdg_conf.join('adopt_redactions.yml'),
92
+ engine: RosettAi.rai_config.default_engine
93
+ )
94
+ end
95
+
96
+ def rules_dir
97
+ RosettAi.paths.rules_dir
98
+ end
99
+
100
+ def run_analysis(adopter)
101
+ unless options[:api]
102
+ log_verbose(t('local_checks'))
103
+ return adopter.evaluate(local_only: true)
104
+ end
105
+
106
+ return run_quorum_analysis(adopter) if options[:quorum]
107
+
108
+ run_remote_analysis(adopter)
109
+ end
110
+
111
+ def run_remote_analysis(adopter)
112
+ spinner = TTY::Spinner.new(
113
+ "[:spinner] #{t('analyzing')}",
114
+ format: :dots,
115
+ clear: true
116
+ )
117
+ spinner.auto_spin
118
+
119
+ outcome = adopter.evaluate(local_only: false)
120
+ if outcome[:cached]
121
+ spinner.success(Rainbow(t('cached')).cyan)
122
+ log_verbose(t('cache_hit'))
123
+ else
124
+ spinner.success(Rainbow(t('done')).green)
125
+ end
126
+ outcome
127
+ rescue StandardError => e
128
+ spinner.error(Rainbow(t('failed')).red)
129
+ raise ::Thor::Error, t('error_prefix', message: e.message)
130
+ end
131
+
132
+ def run_quorum_analysis(adopter)
133
+ strategy = build_quorum_strategy
134
+ prompt = adopter.build_prompt(adopter.discover_managed_files)
135
+ result = run_quorum_with_spinner(strategy, prompt)
136
+ { result: result, sensitive_skipped: adopter.sensitive_files, cached: false }
137
+ end
138
+
139
+ def build_quorum_strategy
140
+ api_key = RosettAi::SecretsResolver.resolve('ANTHROPIC_API_KEY')
141
+ strategy = RosettAi::Quorum::Strategies::Adopt.new(api_key: api_key)
142
+ log_verbose("Quorum engines: #{strategy.eligible_engines.join(', ')}")
143
+ strategy
144
+ end
145
+
146
+ def run_quorum_with_spinner(strategy, prompt)
147
+ spinner = TTY::Spinner.new('[:spinner] Quorum analysis...', format: :dots, clear: true)
148
+ spinner.auto_spin
149
+ result = strategy.run(prompt)
150
+ spinner.success(Rainbow('done').green)
151
+ result
152
+ rescue StandardError => e
153
+ spinner.error(Rainbow('failed').red)
154
+ raise ::Thor::Error, "Quorum analysis failed: #{e.message}"
155
+ end
156
+
157
+ def print_results(outcome)
158
+ result = outcome[:result]
159
+ findings = result['findings'] || []
160
+
161
+ puts
162
+ print_status_line(result['overall_status'] || 'unknown', result['summary'] || '')
163
+ print_findings(findings)
164
+ print_sensitive_skipped(outcome[:sensitive_skipped])
165
+ end
166
+
167
+ def print_findings(findings)
168
+ if findings.empty?
169
+ puts Rainbow(" #{t('no_issues')}").green
170
+ return
171
+ end
172
+
173
+ findings.group_by { |f| f['category'] }.each do |category, items|
174
+ print_category_table(category, items)
175
+ end
176
+ end
177
+
178
+ def print_sensitive_skipped(skipped)
179
+ return unless options[:verbose] && skipped&.any?
180
+
181
+ puts
182
+ puts Rainbow(t('sensitive_skipped_title')).yellow
183
+ skipped.each { |f| puts " #{File.basename(f)}" }
184
+ end
185
+
186
+ def print_status_line(status, summary)
187
+ colored_status = case status
188
+ when 'pass' then Rainbow('PASS').green.bright
189
+ when 'warn' then Rainbow('WARN').yellow.bright
190
+ when 'fail' then Rainbow('FAIL').red.bright
191
+ else Rainbow(status.upcase).white
192
+ end
193
+ puts "Status: #{colored_status} #{summary}"
194
+ puts
195
+ end
196
+
197
+ def print_category_table(category, findings)
198
+ has_engine = findings.any? { |f| f['engine'] || f['engines'] }
199
+ headings = category_headings(has_engine)
200
+ rows = findings.map { |f| finding_row(f, has_engine) }
201
+
202
+ table = ::Terminal::Table.new(
203
+ title: Rainbow(category.capitalize).bright,
204
+ headings: headings, rows: rows,
205
+ style: { border: :unicode_round }
206
+ )
207
+ puts table
208
+ puts
209
+ end
210
+
211
+ def category_headings(has_engine)
212
+ headings = ['Severity', 'File', 'Rule', 'Summary']
213
+ headings << 'Engine' if has_engine
214
+ headings << 'Explanation' if options[:elaborate]
215
+ headings
216
+ end
217
+
218
+ def finding_row(finding, has_engine)
219
+ sanitized = RosettAi::TextSanitizer.sanitize_for_display(finding)
220
+ row = [colorize_severity(sanitized['severity']),
221
+ sanitized['file'] || 'N/A',
222
+ sanitized['rule_id'] || 'N/A',
223
+ sanitized['summary'] || '']
224
+ row << engine_label(finding) if has_engine
225
+ row << (sanitized['explanation'] || '') if options[:elaborate]
226
+ row
227
+ end
228
+
229
+ def engine_label(finding)
230
+ engines = finding['engines'] || [finding['engine']].compact
231
+ engines.join(', ')
232
+ end
233
+
234
+ def colorize_severity(severity)
235
+ case severity
236
+ when 'critical' then Rainbow(severity).red.bright
237
+ when 'high' then Rainbow(severity).red
238
+ when 'medium' then Rainbow(severity).yellow
239
+ when 'low' then Rainbow(severity).cyan
240
+ else severity
241
+ end
242
+ end
243
+
244
+ def log_verbose(message)
245
+ puts " #{message}" if options[:verbose]
246
+ end
247
+ end
248
+ end
249
+ end
250
+ end