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,415 @@
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
+ require 'fileutils'
11
+ require 'yaml'
12
+
13
+ module RosettAi
14
+ module Thor
15
+ module Tasks
16
+ # Context for file compilation operations
17
+ CompileFileContext = Struct.new(:target_path, :content, :checksum, :filename)
18
+
19
+ # Thor task for compiling YAML configuration into markdown rule files
20
+ class Compile < ::Thor
21
+ default_task :generate
22
+
23
+ desc 'generate', 'Compile YAML configurations into markdown rule files'
24
+ long_desc <<~LONGDESC
25
+ Compiles behaviour and design YAML into engine-specific rule files.
26
+ By default, compiles for the configured default engine to ~/.claude/rules/.
27
+
28
+ Flags:
29
+ --verbose: Show detailed compilation output
30
+ --simulate: Dry run with diffs, no file writes
31
+ --vendor: Write compile.lock.yml lockfile
32
+ --engine NAME: Compile for specific engine
33
+ --strict: Treat warnings as errors
34
+ --locales: Compile locale files to gettext/Qt formats
35
+
36
+ Exit codes: 1 if validation fails in --strict mode
37
+
38
+ EXAMPLES
39
+
40
+ raictl compile
41
+ raictl compile --verbose --simulate
42
+ raictl compile --engine generic --strict
43
+ raictl compile --vendor
44
+ raictl compile --locales
45
+
46
+ Related commands: raictl behaviour validate, rai adopt
47
+ LONGDESC
48
+ method_option :verbose,
49
+ type: :boolean,
50
+ default: false,
51
+ desc: 'Show detailed output'
52
+ method_option :vendor,
53
+ type: :boolean,
54
+ default: false,
55
+ desc: 'Write compile.lock.yml lockfile'
56
+ method_option :simulate,
57
+ type: :boolean,
58
+ default: false,
59
+ desc: 'Dry run — show diffs without writing'
60
+ method_option :update,
61
+ type: :boolean,
62
+ default: false,
63
+ desc: 'Update existing rules (default behaviour)'
64
+ method_option :engine,
65
+ type: :string,
66
+ default: nil,
67
+ desc: 'Engine name (e.g., claude, generic)'
68
+ method_option :target,
69
+ type: :string,
70
+ default: nil,
71
+ desc: '[DEPRECATED] Use --engine instead'
72
+ method_option :strict,
73
+ type: :boolean,
74
+ default: false,
75
+ desc: 'Treat warnings as errors'
76
+ method_option :locales,
77
+ type: :boolean,
78
+ default: false,
79
+ desc: 'Compile locale files to gettext and Qt formats'
80
+
81
+ def generate
82
+ if options[:locales]
83
+ compile_locales
84
+ return
85
+ end
86
+
87
+ check_xdg_migration
88
+ compiler = build_compiler
89
+ categories = compiler.discover_categories
90
+
91
+ if categories.empty?
92
+ puts Rainbow(t('compile.no_categories')).yellow
93
+ return
94
+ end
95
+
96
+ log_categories(categories, compiler) if options[:verbose]
97
+
98
+ compiled = compiler.compile
99
+ check_conflict_warnings(compiler)
100
+ check_capabilities(resolve_engine_option)
101
+ results = process_compiled(compiler, compiled)
102
+ remove_orphans(compiler, compiled)
103
+ write_lockfile(compiler, compiled) if options[:vendor] && !options[:simulate]
104
+ print_summary(results)
105
+ end
106
+
107
+ private
108
+
109
+ def t(key, **)
110
+ ::I18n.t("rosett_ai.cli.#{key}", **)
111
+ end
112
+
113
+ def build_compiler
114
+ engine_name = resolve_engine_option
115
+ backend = RosettAi::Compiler::Backend.for(engine_name)
116
+ scope = detect_compile_scope
117
+ target = resolve_target_dir(backend) || default_target_dir(scope)
118
+ RosettAi::Compiler::CompilationPipeline.new(
119
+ source_dir: resolve_source_dir(scope),
120
+ target_dir: target,
121
+ schema_dir: RosettAi.root.join('conf', 'schemas'),
122
+ backend: backend,
123
+ scope: scope,
124
+ additional_source_dirs: additional_dirs(scope)
125
+ )
126
+ end
127
+
128
+ # Resolves the primary source directory for compilation.
129
+ #
130
+ # Global scope: XDG-compliant ~/.config/rosett-ai/conf (user behaviours)
131
+ # Project scope: .rosett-ai/conf (project behaviours, merged with global via additional_source_dirs)
132
+ # Internal (rosett-ai dev): rosett-ai repo's own conf/ (for development)
133
+ def resolve_source_dir(scope)
134
+ return RosettAi.root.join('conf') if RosettAi.context.rai_internal?
135
+
136
+ scope == :project ? RosettAi.conf_root.join('conf') : global_conf_dir
137
+ end
138
+
139
+ # Additional source dirs for the 3-tier lookup chain.
140
+ # Order matters: later dirs override earlier ones (CompilationPipeline
141
+ # appends @source_dir last). So packaged defaults go first, then XDG
142
+ # user config, so user rules override packaged defaults by filename.
143
+ def additional_dirs(scope)
144
+ dirs = []
145
+ packaged = RosettAi.paths.packaged_conf_dir
146
+ dirs << packaged if packaged.directory?
147
+ dirs << global_conf_dir if scope == :project || RosettAi.context.rai_internal?
148
+ dirs
149
+ end
150
+
151
+ def global_conf_dir
152
+ RosettAi.paths.rai_conf_dir
153
+ end
154
+
155
+ def detect_compile_scope
156
+ ctx = RosettAi.context
157
+ ctx.project? && !ctx.rai_internal? ? :project : :global
158
+ end
159
+
160
+ def check_xdg_migration
161
+ return if RosettAi.context.rai_internal?
162
+
163
+ migrator = RosettAi::Migration::XdgMigrator.new
164
+ return unless migrator.migration_needed?
165
+
166
+ puts Rainbow(migrator.warning_message).yellow
167
+ migrated = migrator.migrate!
168
+ migrated.each { |f| puts Rainbow(" Migrated: #{f}").green }
169
+ puts Rainbow(" Migration complete. #{migrated.size} file(s) moved.").green unless migrated.empty?
170
+ end
171
+
172
+ def check_conflict_warnings(compiler)
173
+ warnings = compiler.conflict_warnings
174
+ return if warnings.empty?
175
+
176
+ warnings.each do |warning|
177
+ puts Rainbow(" WARNING: #{warning}").yellow
178
+ end
179
+
180
+ return unless options[:strict]
181
+
182
+ raise RosettAi::CompileError,
183
+ "#{warnings.size} duplicate rule ID(s) detected in strict mode"
184
+ end
185
+
186
+ def check_capabilities(engine_name)
187
+ checker = RosettAi::Compiler::CapabilityChecker.new(engine_name)
188
+ summaries = checker.check_summarized(RosettAi.conf_root.join('conf'))
189
+ return if summaries.empty?
190
+
191
+ summaries.each do |summary|
192
+ puts Rainbow(" WARNING: #{summary[:message]}").yellow
193
+ end
194
+
195
+ return unless options[:strict]
196
+
197
+ total = summaries.sum { |summary| summary[:count] }
198
+ raise RosettAi::CompileError,
199
+ "#{total} capability warning(s) in strict mode"
200
+ end
201
+
202
+ def resolve_engine_option
203
+ engine = options[:engine]
204
+ target = options[:target]
205
+
206
+ if engine && target
207
+ raise RosettAi::CompileError,
208
+ t('compile.engine_and_target_conflict')
209
+ end
210
+
211
+ if target
212
+ if options[:strict]
213
+ raise RosettAi::CompileError,
214
+ t('compile.target_deprecated_strict')
215
+ end
216
+ RosettAi::Deprecation.warn_deprecated(
217
+ feature: '--target',
218
+ replacement: '--engine',
219
+ since: '1.0.0',
220
+ removal: '2.0.0'
221
+ )
222
+ return target
223
+ end
224
+
225
+ engine || RosettAi.rai_config.default_engine
226
+ end
227
+
228
+ def resolve_target_dir(backend)
229
+ dir = backend.profile.output_dir
230
+ dir ? Pathname.new(File.expand_path(dir)) : nil
231
+ end
232
+
233
+ def default_target_dir(scope)
234
+ return RosettAi.context.project_root.join('.claude', 'rules') if scope == :project
235
+
236
+ RosettAi.paths.rules_dir
237
+ end
238
+
239
+ # Resolves the target directory for a compiled output based on its
240
+ # declared scope. Falls back to the pipeline's target_dir when no
241
+ # scope is declared or the behaviour's scope matches the pipeline's.
242
+ #
243
+ # Scope routing:
244
+ # global/personal → ~/.claude/rules/ (global rules dir)
245
+ # project → .claude/rules/ (project-local)
246
+ # nil → pipeline default (current behaviour)
247
+ def resolve_scoped_target_dir(compiler, info)
248
+ declared_scope = info[:scope]
249
+ return compiler.target_dir unless declared_scope
250
+
251
+ case declared_scope
252
+ when 'project'
253
+ if RosettAi.context.project?
254
+ RosettAi.context.project_root.join('.claude', 'rules')
255
+ else
256
+ compiler.target_dir
257
+ end
258
+ when 'global', 'personal'
259
+ RosettAi.paths.rules_dir
260
+ else
261
+ compiler.target_dir
262
+ end
263
+ end
264
+
265
+ def compile_locales
266
+ locale_compiler = RosettAi::Compiler::LocaleCompiler.new(
267
+ source_dir: RosettAi.root.join('locales'),
268
+ gettext_dir: RosettAi.root.join('locales', 'compiled', 'gettext'),
269
+ qt_dir: RosettAi.root.join('locales', 'compiled', 'qt')
270
+ )
271
+
272
+ if options[:simulate]
273
+ puts Rainbow(t('compile.locale_simulate')).yellow
274
+ log_verbose("Source: #{locale_compiler.source_dir}")
275
+ log_verbose("Gettext output: #{locale_compiler.gettext_dir}")
276
+ log_verbose("Qt output: #{locale_compiler.qt_dir}")
277
+ return
278
+ end
279
+
280
+ results = locale_compiler.compile
281
+ puts Rainbow(t('compile.locale_gettext_compiled', count: results[:gettext].size)).green
282
+ puts Rainbow(t('compile.locale_qt_compiled', count: results[:qt].size)).green
283
+ end
284
+
285
+ def log_categories(categories, compiler)
286
+ puts Rainbow(t('compile.discovered_categories')).bright
287
+ categories.each { |cat| puts " #{Rainbow(cat).cyan}" }
288
+ compiler.skipped_project_categories.each do |cat|
289
+ puts " #{Rainbow(cat).dark} (project scope only)"
290
+ end
291
+ puts
292
+ end
293
+
294
+ def process_compiled(compiler, compiled)
295
+ results = { created: [], updated: [], unchanged: [], skipped: [] }
296
+
297
+ compiled.each do |filename, info|
298
+ target_dir = resolve_scoped_target_dir(compiler, info)
299
+ target_path = target_dir.join(filename)
300
+ content = info[:content]
301
+ new_checksum = compiler.checksum(content)
302
+
303
+ ctx = CompileFileContext.new(
304
+ target_path: target_path, content: content, checksum: new_checksum, filename: filename
305
+ )
306
+ if options[:simulate]
307
+ handle_simulate(compiler, ctx, results)
308
+ else
309
+ handle_write(compiler, ctx, results)
310
+ end
311
+ end
312
+
313
+ results
314
+ end
315
+
316
+ def handle_simulate(compiler, ctx, results)
317
+ if File.exist?(ctx.target_path)
318
+ diff_output = compiler.diff(ctx.target_path, ctx.content)
319
+ if diff_output
320
+ puts Rainbow(t('compile.would_update', filename: ctx.filename)).yellow
321
+ puts diff_output if options[:verbose]
322
+ results[:updated] << ctx.filename
323
+ else
324
+ puts Rainbow(t('compile.unchanged', filename: ctx.filename)).green if options[:verbose]
325
+ results[:unchanged] << ctx.filename
326
+ end
327
+ else
328
+ puts Rainbow(t('compile.would_create', filename: ctx.filename)).cyan
329
+ results[:created] << ctx.filename
330
+ end
331
+ end
332
+
333
+ def handle_write(compiler, ctx, results)
334
+ FileUtils.mkdir_p(ctx.target_path.dirname)
335
+ action = determine_write_action(compiler, ctx)
336
+ perform_write_action(ctx, action, results)
337
+ end
338
+
339
+ def determine_write_action(compiler, ctx)
340
+ return :created unless File.exist?(ctx.target_path)
341
+
342
+ existing = compiler.checksum(File.read(ctx.target_path, encoding: 'utf-8'))
343
+ existing == ctx.checksum ? :unchanged : :updated
344
+ end
345
+
346
+ def perform_write_action(ctx, action, results)
347
+ target_path, content, filename, checksum_prefix = extract_ctx(ctx)
348
+ write_file(target_path, content) if [:updated, :created].include?(action)
349
+ log_verbose("#{format_action_label(action)}: #{filename} (#{checksum_prefix})")
350
+ results[action] << filename
351
+ end
352
+
353
+ def format_action_label(action)
354
+ { created: t('compile.created'), updated: t('compile.updated'),
355
+ unchanged: t('compile.unchanged', filename: '') }.fetch(action, action.to_s)
356
+ end
357
+
358
+ def extract_ctx(ctx)
359
+ [ctx.target_path, ctx.content, ctx.filename, ctx.checksum[0..7]]
360
+ end
361
+
362
+ def write_file(path, content)
363
+ File.open(path, 'w', 0o644) { |f| f.write(content) }
364
+ end
365
+
366
+ def remove_orphans(compiler, compiled)
367
+ # Guard: when compile produced no output (e.g. empty project conf/),
368
+ # every managed file in target_dir would appear orphaned. Removing
369
+ # them would silently destroy all compiled rules. See WP#712.
370
+ return if compiled.empty?
371
+
372
+ orphans = compiler.orphaned_files(compiled.keys)
373
+ orphans.each do |orphan|
374
+ basename = File.basename(orphan)
375
+ if options[:simulate]
376
+ puts Rainbow(t('compile.would_remove_orphan', filename: basename)).red
377
+ else
378
+ File.delete(orphan)
379
+ log_verbose(t('compile.removed_orphan', filename: basename))
380
+ end
381
+ end
382
+ end
383
+
384
+ def write_lockfile(compiler, compiled)
385
+ lockfile_path = RosettAi.conf_root.join('conf', 'compile.lock.yml')
386
+ data = compiler.lockfile_data(compiled)
387
+ File.open(lockfile_path, 'w', 0o644) { |f| f.write(data.to_yaml) }
388
+ log_verbose(t('compile.wrote_lockfile', path: lockfile_path))
389
+ end
390
+
391
+ def print_summary(results)
392
+ rows = []
393
+ rows << [t('compile.created'), results[:created].size] unless results[:created].empty?
394
+ rows << [t('compile.updated'), results[:updated].size] unless results[:updated].empty?
395
+ rows << [t('compile.unchanged', filename: ''), results[:unchanged].size] unless results[:unchanged].empty?
396
+
397
+ return if rows.empty?
398
+
399
+ puts
400
+ table = ::Terminal::Table.new(
401
+ title: Rainbow(t('compile.summary_title')).bright,
402
+ headings: [t('compile.action'), t('compile.count')],
403
+ rows: rows,
404
+ style: { border: :unicode_round }
405
+ )
406
+ puts table
407
+ end
408
+
409
+ def log_verbose(message)
410
+ puts " #{message}" if options[:verbose]
411
+ end
412
+ end
413
+ end
414
+ end
415
+ end
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-License-Identifier: GPL-3.0-only
4
+ # Copyright (C) 2026 Hugo Antonio Sepulveda Manriquez / NeatNerds
5
+
6
+ module RosettAi
7
+ module Thor
8
+ module Tasks
9
+ # Thor subcommand for generating shell completion scripts.
10
+ #
11
+ # Generates bash, zsh, and fish completion scripts from the Thor
12
+ # command registry. Scripts are output to stdout for piping or
13
+ # can be installed to the standard completion directory.
14
+ #
15
+ # @author hugo
16
+ # @author claude
17
+ class Completion < ::Thor
18
+ INSTALL_PATHS = {
19
+ 'bash' => '/usr/share/bash-completion/completions/rai',
20
+ 'zsh' => '/usr/share/zsh/vendor-completions/_rai',
21
+ 'fish' => "#{Dir.home}/.config/fish/completions/raictl.fish"
22
+ }.freeze
23
+
24
+ desc 'bash', 'Generate bash completion script'
25
+ long_desc <<~LONGDESC
26
+ Generate a bash completion script and write to stdout.
27
+
28
+ Example: raictl completion bash > /usr/share/bash-completion/completions/rai
29
+
30
+ Related: completion zsh, completion fish, completion install
31
+ LONGDESC
32
+ def bash
33
+ output_completion('bash')
34
+ end
35
+
36
+ desc 'zsh', 'Generate zsh completion script'
37
+ long_desc <<~LONGDESC
38
+ Generate a zsh completion script and write to stdout.
39
+
40
+ Example: raictl completion zsh > /usr/share/zsh/vendor-completions/_rai
41
+
42
+ Related: completion bash, completion fish, completion install
43
+ LONGDESC
44
+ def zsh
45
+ output_completion('zsh')
46
+ end
47
+
48
+ desc 'fish', 'Generate fish completion script'
49
+ long_desc <<~LONGDESC
50
+ Generate a fish completion script and write to stdout.
51
+
52
+ Example: raictl completion fish > ~/.config/fish/completions/raictl.fish
53
+
54
+ Related: completion bash, completion zsh, completion install
55
+ LONGDESC
56
+ def fish
57
+ output_completion('fish')
58
+ end
59
+
60
+ desc 'install', 'Install completion script for the current shell'
61
+ long_desc <<~LONGDESC
62
+ Install the completion script to the standard system path.
63
+
64
+ The shell is auto-detected from $SHELL unless overridden
65
+ with --shell. Supported shells: bash, zsh, fish.
66
+
67
+ Install paths:
68
+ bash: /usr/share/bash-completion/completions/rai
69
+ zsh: /usr/share/zsh/vendor-completions/_rai
70
+ fish: ~/.config/fish/completions/raictl.fish
71
+
72
+ EXAMPLES
73
+
74
+ raictl completion install
75
+ raictl completion install --shell zsh
76
+
77
+ Related: completion bash, completion zsh, completion fish
78
+ LONGDESC
79
+ method_option :shell, type: :string, default: nil,
80
+ desc: 'Shell to install for (auto-detected from $SHELL)'
81
+ def install
82
+ shell = detect_shell
83
+ generator = RosettAi::Completion::Generator.new
84
+ script = generator.generate(shell)
85
+ path = INSTALL_PATHS.fetch(shell)
86
+
87
+ install_script(script, path, shell)
88
+ end
89
+
90
+ private
91
+
92
+ def output_completion(shell)
93
+ generator = RosettAi::Completion::Generator.new
94
+ puts generator.generate(shell)
95
+ end
96
+
97
+ def detect_shell
98
+ shell = options[:shell] || File.basename(ENV.fetch('SHELL', ''))
99
+ shell = shell.downcase
100
+
101
+ unless RosettAi::Completion::Generator::SUPPORTED_SHELLS.include?(shell)
102
+ raise RosettAi::Error,
103
+ "Shell #{shell} is not supported — available: " \
104
+ "#{RosettAi::Completion::Generator::SUPPORTED_SHELLS.join(', ')}"
105
+ end
106
+
107
+ shell
108
+ end
109
+
110
+ def install_script(script, path, shell)
111
+ dir = File.dirname(path)
112
+ FileUtils.mkdir_p(dir) unless File.directory?(dir)
113
+
114
+ File.write(path, script)
115
+ warn "Installed #{shell} completion to #{path}"
116
+ rescue Errno::EACCES
117
+ raise RosettAi::Error,
118
+ "Permission denied writing to #{path} — try: sudo rai completion install --shell #{shell}"
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,82 @@
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
+ # CLI task for `rai comply` — compliance verification.
12
+ #
13
+ # Runs CRA, license, and SPDX header compliance checks.
14
+ # Produces TTY-aware or machine-readable (JSON) output.
15
+ # Exit codes: 0 = all pass, 1 = warnings only, 2 = any failure.
16
+ #
17
+ # @author hugo
18
+ # @author claude
19
+ class Comply < ::Thor
20
+ default_task :check
21
+
22
+ desc 'check', 'Run all compliance checks'
23
+ long_desc <<~LONGDESC
24
+ Runs compliance checks and produces a summary report.
25
+
26
+ By default, all checks are run: CRA requirements, license
27
+ compatibility, and SPDX header verification.
28
+
29
+ Use --cra, --license, or --headers to run specific checks.
30
+ Use --format json for machine-readable CI output.
31
+
32
+ Exit codes: 0 = all pass, 1 = warnings only, 2 = any failure.
33
+
34
+ EXAMPLES
35
+
36
+ raictl comply
37
+ raictl comply --cra
38
+ raictl comply --license --headers
39
+ raictl comply --format json
40
+ LONGDESC
41
+ method_option :cra, type: :boolean, default: false,
42
+ desc: 'Run CRA-specific checks only'
43
+ method_option :license, type: :boolean, default: false,
44
+ desc: 'Run license audit checks only'
45
+ method_option :headers, type: :boolean, default: false,
46
+ desc: 'Run SPDX header checks only'
47
+ method_option :format, type: :string, enum: ['table', 'json'], default: 'table',
48
+ desc: 'Output format'
49
+ def check
50
+ checks = selected_checks
51
+ results = RosettAi::Comply::Runner.new(
52
+ project_root: resolve_project_root,
53
+ checks: checks
54
+ ).run
55
+
56
+ reporter = RosettAi::Comply::Reporter.new(
57
+ results: results,
58
+ format: options[:format]
59
+ )
60
+ reporter.report
61
+
62
+ code = reporter.exit_code
63
+ exit code unless code.zero?
64
+ end
65
+
66
+ private
67
+
68
+ def selected_checks
69
+ active = []
70
+ active << :cra if options[:cra]
71
+ active << :license if options[:license]
72
+ active << :headers if options[:headers]
73
+ active.empty? ? nil : active
74
+ end
75
+
76
+ def resolve_project_root
77
+ Pathname.new(ENV.fetch('RAI_ORIGINAL_PWD', Dir.pwd))
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end