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,175 @@
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 GitHooks
8
+ # Installs and uninstalls rosett-ai-managed git hooks in .git/hooks/.
9
+ #
10
+ # Reads hook definitions from .rosett-ai/config.yml and generates POSIX sh
11
+ # scripts that invoke rai commands. Chains to pre-existing hooks
12
+ # (overcommit, husky, lefthook) without overwriting them.
13
+ #
14
+ # @author hugo
15
+ # @author claude
16
+ class Installer
17
+ RAI_MARKER = '# rosett-ai-managed hook'
18
+ BACKUP_SUFFIX = '.rosett-ai-backup'
19
+
20
+ SUPPORTED_HOOKS = [
21
+ 'pre-commit', 'post-commit', 'post-checkout', 'post-merge', 'pre-push'
22
+ ].freeze
23
+
24
+ # @param project_root [Pathname] path to the project root
25
+ # @param hooks_config [Hash] hook definitions from .rosett-ai/config.yml
26
+ def initialize(project_root:, hooks_config:)
27
+ @project_root = Pathname.new(project_root)
28
+ @hooks_config = hooks_config
29
+ @hooks_dir = @project_root.join('.git', 'hooks')
30
+ end
31
+
32
+ # Installs all configured hooks.
33
+ #
34
+ # @return [Hash] result with :installed, :skipped, :errors keys
35
+ def install
36
+ results = { installed: [], skipped: [], errors: [] }
37
+
38
+ unless @hooks_dir.directory?
39
+ results[:errors] << 'Not a git repository (missing .git/hooks/)'
40
+ return results
41
+ end
42
+
43
+ @hooks_config.each do |hook_name, commands|
44
+ normalized = normalize_hook_name(hook_name)
45
+ install_hook(normalized, Array(commands), results)
46
+ end
47
+
48
+ results
49
+ end
50
+
51
+ # Uninstalls all rosett-ai-managed hooks, restoring backups.
52
+ #
53
+ # @return [Hash] result with :removed, :restored, :errors keys
54
+ def uninstall
55
+ results = { removed: [], restored: [], errors: [] }
56
+
57
+ unless @hooks_dir.directory?
58
+ results[:errors] << 'Not a git repository (missing .git/hooks/)'
59
+ return results
60
+ end
61
+
62
+ SUPPORTED_HOOKS.each { |hook| uninstall_hook(hook, results) }
63
+ results
64
+ end
65
+
66
+ # Lists currently installed rai hooks.
67
+ #
68
+ # @return [Array<Hash>] list of hook info hashes with :name and :commands
69
+ def list
70
+ SUPPORTED_HOOKS.filter_map do |hook|
71
+ path = @hooks_dir.join(hook)
72
+ next unless path.exist? && rai_managed?(path)
73
+
74
+ { name: hook, commands: extract_commands(path) }
75
+ end
76
+ end
77
+
78
+ # Reports drift between config and installed hooks.
79
+ #
80
+ # @return [Hash] status with :in_sync, :missing, :extra, :drifted keys
81
+ def status
82
+ result = { in_sync: [], missing: [], extra: [], drifted: [] }
83
+
84
+ configured = @hooks_config.transform_keys { |key| normalize_hook_name(key) }
85
+
86
+ configured.each do |hook_name, commands|
87
+ path = @hooks_dir.join(hook_name)
88
+ classify_hook(path, hook_name, Array(commands), result)
89
+ end
90
+
91
+ find_extra_hooks(configured, result)
92
+ result
93
+ end
94
+
95
+ private
96
+
97
+ def normalize_hook_name(name)
98
+ name.to_s.tr('_', '-')
99
+ end
100
+
101
+ def install_hook(hook_name, commands, results)
102
+ unless SUPPORTED_HOOKS.include?(hook_name)
103
+ results[:errors] << "Unsupported hook: #{hook_name}"
104
+ return
105
+ end
106
+
107
+ path = @hooks_dir.join(hook_name)
108
+ backup_existing_hook(path) if path.exist? && !rai_managed?(path)
109
+
110
+ script = ScriptGenerator.generate(hook_name: hook_name, commands: commands)
111
+ File.write(path, script)
112
+ File.chmod(0o755, path)
113
+ results[:installed] << hook_name
114
+ end
115
+
116
+ def uninstall_hook(hook_name, results)
117
+ path = @hooks_dir.join(hook_name)
118
+ return unless path.exist? && rai_managed?(path)
119
+
120
+ File.delete(path)
121
+ results[:removed] << hook_name
122
+
123
+ backup = Pathname.new("#{path}#{BACKUP_SUFFIX}")
124
+ return unless backup.exist?
125
+
126
+ FileUtils.mv(backup, path)
127
+ results[:restored] << hook_name
128
+ end
129
+
130
+ def backup_existing_hook(path)
131
+ backup = Pathname.new("#{path}#{BACKUP_SUFFIX}")
132
+ FileUtils.cp(path, backup) unless backup.exist?
133
+ end
134
+
135
+ def rai_managed?(path)
136
+ File.read(path).include?(RAI_MARKER)
137
+ end
138
+
139
+ def extract_commands(path)
140
+ File.read(path).scan(/^# Run: rosett-ai (.+)$/).flatten
141
+ end
142
+
143
+ def classify_hook(path, hook_name, commands, result)
144
+ unless path.exist?
145
+ result[:missing] << hook_name
146
+ return
147
+ end
148
+
149
+ unless rai_managed?(path)
150
+ result[:missing] << hook_name
151
+ return
152
+ end
153
+
154
+ installed_cmds = extract_commands(path)
155
+ expected_cmds = commands.map(&:to_s)
156
+
157
+ if installed_cmds == expected_cmds
158
+ result[:in_sync] << hook_name
159
+ else
160
+ result[:drifted] << hook_name
161
+ end
162
+ end
163
+
164
+ def find_extra_hooks(configured, result)
165
+ SUPPORTED_HOOKS.each do |hook|
166
+ path = @hooks_dir.join(hook)
167
+ next unless path.exist? && rai_managed?(path)
168
+ next if configured.key?(hook)
169
+
170
+ result[:extra] << hook
171
+ end
172
+ end
173
+ end
174
+ end
175
+ end
@@ -0,0 +1,125 @@
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 'shellwords'
7
+
8
+ module RosettAi
9
+ module GitHooks
10
+ # Generates POSIX sh hook scripts that invoke rai commands.
11
+ #
12
+ # Scripts include timeout handling, RAI_SKIP_HOOKS bypass,
13
+ # and chaining to pre-existing hooks via the backup mechanism.
14
+ #
15
+ # @author hugo
16
+ # @author claude
17
+ module ScriptGenerator
18
+ BACKUP_SUFFIX = '.rosett-ai-backup'
19
+ DEFAULT_TIMEOUT = 30
20
+
21
+ # Pattern matching shell metacharacters that indicate potential injection.
22
+ # Matches: ; | & ` $( )
23
+ DANGEROUS_PATTERN = /[;|&`]|\$\(/
24
+
25
+ # Validates a command string for shell safety.
26
+ #
27
+ # @param command [String] the command to validate
28
+ # @raise [ArgumentError] if the command contains dangerous shell metacharacters
29
+ # @return [void]
30
+ def self.validate_command!(command)
31
+ return unless command.match?(DANGEROUS_PATTERN)
32
+
33
+ raise ArgumentError,
34
+ "Hook command contains dangerous shell metacharacters: #{command.inspect}"
35
+ end
36
+
37
+ # Generates a POSIX sh hook script.
38
+ #
39
+ # @param hook_name [String] the git hook name (e.g. 'pre-commit')
40
+ # @param commands [Array<String>] rai commands to run
41
+ # @param timeout [Integer] timeout in seconds (default 30)
42
+ # @return [String] the generated script content
43
+ # @raise [ArgumentError] if any command contains shell metacharacters
44
+ def self.generate(hook_name:, commands:, timeout: DEFAULT_TIMEOUT)
45
+ commands.each { |cmd| validate_command!(cmd) }
46
+ blocking = blocking_hook?(hook_name)
47
+
48
+ lines = [shebang, marker, skip_check, chain_call(hook_name)]
49
+ commands.each { |cmd| lines << command_block(cmd, timeout, blocking) }
50
+ lines << exit_line
51
+ lines.join("\n")
52
+ end
53
+
54
+ # @param hook_name [String]
55
+ # @return [Boolean] true if the hook should block the git operation on failure
56
+ def self.blocking_hook?(hook_name)
57
+ ['pre-commit', 'pre-push'].include?(hook_name)
58
+ end
59
+
60
+ def self.shebang
61
+ '#!/bin/sh'
62
+ end
63
+ private_class_method :shebang
64
+
65
+ def self.marker
66
+ "#{Installer::RAI_MARKER} — do not edit manually"
67
+ end
68
+ private_class_method :marker
69
+
70
+ def self.skip_check
71
+ <<~SH
72
+
73
+ # Emergency bypass
74
+ if [ "${RAI_SKIP_HOOKS:-0}" = "1" ]; then
75
+ exit 0
76
+ fi
77
+ SH
78
+ end
79
+ private_class_method :skip_check
80
+
81
+ def self.chain_call(hook_name)
82
+ <<~SH
83
+ # Chain to pre-existing hook if backed up
84
+ if [ -x "$(dirname "$0")/#{hook_name}#{BACKUP_SUFFIX}" ]; then
85
+ "$(dirname "$0")/#{hook_name}#{BACKUP_SUFFIX}" "$@"
86
+ chain_status=$?
87
+ if [ $chain_status -ne 0 ]; then
88
+ exit $chain_status
89
+ fi
90
+ fi
91
+ SH
92
+ end
93
+ private_class_method :chain_call
94
+
95
+ def self.command_block(command, timeout, blocking)
96
+ exit_line = blocking ? ' exit $cmd_status' : ' echo "rosett-ai: hook command failed (non-blocking)" >&2'
97
+ escaped = Shellwords.shellescape(command)
98
+
99
+ <<~SH
100
+ # Run: rosett-ai #{command}
101
+ if command -v timeout >/dev/null 2>&1; then
102
+ timeout #{timeout} rosett-ai #{escaped}
103
+ else
104
+ raictl #{escaped}
105
+ fi
106
+ cmd_status=$?
107
+ if [ $cmd_status -ne 0 ]; then
108
+ if [ $cmd_status -eq 124 ]; then
109
+ echo "rosett-ai: #{command} timed out after #{timeout}s. Run manually or set RAI_SKIP_HOOKS=1." >&2
110
+ #{' exit 1' if blocking}
111
+ else
112
+ #{exit_line}
113
+ fi
114
+ fi
115
+ SH
116
+ end
117
+ private_class_method :command_block
118
+
119
+ def self.exit_line
120
+ 'exit 0'
121
+ end
122
+ private_class_method :exit_line
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-License-Identifier: GPL-3.0-only
4
+ # Copyright (C) 2026 Hugo Antonio Sepulveda Manriquez / NeatNerds
5
+
6
+ require 'yaml'
7
+ require 'pathname'
8
+
9
+ module RosettAi
10
+ module Gitlab
11
+ module Validators
12
+ # Validates GitLab CI YAML files for syntax correctness.
13
+ #
14
+ # Uses Psych.parse (AST-only parsing) rather than YAML.safe_load so that
15
+ # GitLab CI custom tags like !reference are accepted without needing
16
+ # explicit constructors. This catches genuine YAML syntax errors
17
+ # (e.g. double-quoted strings misinterpreted as YAML scalars) while
18
+ # remaining compatible with GitLab-specific extensions.
19
+ class SupplementaryGitlabCiYamlValidator
20
+ CI_ROOT_FILE = '.gitlab-ci.yml'
21
+ CI_FILES_DIR = '.gitlab-ci-files'
22
+
23
+ attr_reader :results
24
+
25
+ def initialize(project_dir: Dir.pwd)
26
+ @project_dir = Pathname.new(project_dir)
27
+ @results = { valid: [], invalid: [] }
28
+ end
29
+
30
+ def validate
31
+ @results = { valid: [], invalid: [] }
32
+
33
+ files = ci_files
34
+ return @results if files.empty?
35
+
36
+ files.each { |file| validate_file(file) }
37
+ @results
38
+ end
39
+
40
+ def valid?
41
+ validate if @results[:valid].empty? && @results[:invalid].empty?
42
+ @results[:invalid].empty?
43
+ end
44
+
45
+ private
46
+
47
+ def ci_files
48
+ files = []
49
+
50
+ root_ci = @project_dir.join(CI_ROOT_FILE)
51
+ files << root_ci if root_ci.exist?
52
+
53
+ ci_dir = @project_dir.join(CI_FILES_DIR)
54
+ files.concat(ci_dir.glob('**/*.yml').sort) if ci_dir.exist?
55
+
56
+ files
57
+ end
58
+
59
+ def validate_file(file)
60
+ content = File.read(file)
61
+ Psych.parse(content)
62
+ @results[:valid] << { file: relative_path(file), status: :ok }
63
+ rescue Psych::SyntaxError => e
64
+ @results[:invalid] << {
65
+ file: relative_path(file),
66
+ status: :error,
67
+ message: e.message,
68
+ line: e.line,
69
+ column: e.column
70
+ }
71
+ end
72
+
73
+ def relative_path(file)
74
+ file.relative_path_from(@project_dir).to_s
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,46 @@
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 I18n
8
+ # Resolves the active locale using priority:
9
+ # explicit flag > config > LANG env > "en".
10
+ #
11
+ # Provides a fallback chain for graceful degradation:
12
+ # nl_BE -> nl -> en, ar_EG -> ar -> en, etc.
13
+ class LocaleResolver
14
+ # Resolves the active locale from explicit flag, env, or default.
15
+ #
16
+ # @param explicit [String, nil] explicit locale override
17
+ # @param env [Hash{String => String}] environment variables (defaults to ENV)
18
+ # @return [Hash{Symbol => Object}] hash with :locale and :chain keys
19
+ def resolve(explicit: nil, env: ENV)
20
+ locale = explicit || parse_lang(env['LANG']) || 'en'
21
+ { locale: locale, chain: fallback_chain(locale) }
22
+ end
23
+
24
+ # Builds a fallback chain from a locale string (e.g. "nl_BE" -> ["nl_BE", "nl", "en"]).
25
+ #
26
+ # @param locale [String] locale identifier
27
+ # @return [Array<String>] ordered fallback chain ending with "en"
28
+ def fallback_chain(locale)
29
+ parts = locale.split('_')
30
+ chain = [locale]
31
+ chain << parts.first if parts.length > 1
32
+ chain << 'en' unless chain.include?('en')
33
+ chain.uniq
34
+ end
35
+
36
+ private
37
+
38
+ def parse_lang(lang_value)
39
+ return nil if lang_value.nil? || lang_value.empty?
40
+
41
+ # LANG is typically "en_US.UTF-8" — extract the locale part
42
+ lang_value.split('.').first
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,32 @@
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 I18n
8
+ # Validates that the runtime environment supports UTF-8 encoding.
9
+ #
10
+ # Called early in CLI startup to fail fast with a clear error
11
+ # rather than producing garbled output.
12
+ class Utf8Checker
13
+ # Exits with an error if the runtime is not UTF-8.
14
+ #
15
+ # @return [void]
16
+ def self.check!
17
+ return if utf8_environment?
18
+
19
+ warn "rai requires a UTF-8 terminal. Current: #{Encoding.default_external}"
20
+ warn 'Set: export LANG=en_US.UTF-8'
21
+ exit 1
22
+ end
23
+
24
+ # Whether the default external encoding is UTF-8.
25
+ #
26
+ # @return [Boolean]
27
+ def self.utf8_environment?
28
+ Encoding.default_external == Encoding::UTF_8
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,24 @@
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 Init
8
+ # Writes the default rai config file at ~/.config/rosett-ai/config.yml
9
+ # if it does not already exist.
10
+ class ConfigFileWriter
11
+ def self.write_default(config_dir: nil)
12
+ dir = config_dir || RosettAi.paths.rai_config_dir
13
+ path = dir.join('config.yml')
14
+
15
+ return { action: :unchanged, path: path } if path.exist?
16
+
17
+ FileUtils.mkdir_p(dir)
18
+ content = RosettAi::RaiConfig::DEFAULTS.to_yaml
19
+ File.open(path, 'w', 0o644) { |f| f.write(content) }
20
+ { action: :created, path: path }
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-License-Identifier: GPL-3.0-only
4
+ # Copyright (C) 2026 Hugo Antonio Sepulveda Manriquez / NeatNerds
5
+
6
+ require 'fileutils'
7
+
8
+ module RosettAi
9
+ module Init
10
+ # Helper module for creating directories during initialization
11
+ module DirectoryBuilder
12
+ GLOBAL_DIRS = ['rules', 'locales', 'conf', 'conf/behaviour', 'conf/schemas', 'doc', 'bin', 'lib',
13
+ 'lib/rosett-ai'].freeze
14
+ LOCAL_DIRS = ['rules', 'conf', 'conf/behaviour', 'conf/schemas'].freeze
15
+
16
+ module_function
17
+
18
+ def create_global_structure(base_dir)
19
+ create_directories(base_dir, GLOBAL_DIRS)
20
+ end
21
+
22
+ def create_local_structure(base_dir)
23
+ create_directories(base_dir, LOCAL_DIRS)
24
+ end
25
+
26
+ def create_directories(base_dir, subdirs)
27
+ created = []
28
+ ([base_dir] + subdirs.map { |subdir| File.join(base_dir, subdir) }).each do |dir|
29
+ next if Dir.exist?(dir)
30
+
31
+ FileUtils.mkdir_p(dir)
32
+ created << dir
33
+ end
34
+ created
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-License-Identifier: GPL-3.0-only
4
+ # Copyright (C) 2026 Hugo Antonio Sepulveda Manriquez / NeatNerds
5
+
6
+ require 'fileutils'
7
+
8
+ module RosettAi
9
+ module Init
10
+ # Helper module for copying files during initialization
11
+ module FileCopier
12
+ module_function
13
+
14
+ def copy_global_assets(base_dir)
15
+ root = RosettAi.root
16
+ copied = []
17
+ copy_single_file(root.join('settings.json'), File.join(base_dir, 'settings.json'), copied,
18
+ 'settings.json')
19
+ copy_executable(root.join('bin', 'rai'), File.join(base_dir, 'bin', 'rai'), copied, 'bin/raictl')
20
+ copy_directory(root.join('lib', 'rosett_ai'), File.join(base_dir, 'lib', 'rosett_ai'), copied, 'lib/rosett_ai/')
21
+ # Conf assets (behaviours, schemas) go to XDG-compliant ~/.config/rosett-ai/conf/
22
+ xdg_conf = RosettAi.paths.rai_conf_dir.to_s
23
+ copy_conf_assets(root, xdg_conf, copied)
24
+ copy_glob_files(root.join('doc'), File.join(base_dir, 'doc'), '*.md', 'doc', copied)
25
+ copied
26
+ end
27
+
28
+ def copy_local_assets(base_dir)
29
+ copied = []
30
+ # Local scope copies to .claude/conf/ (not XDG)
31
+ copy_conf_assets(RosettAi.root, File.join(base_dir, 'conf'), copied)
32
+ copied
33
+ end
34
+
35
+ def copy_conf_assets(source_root, conf_base, copied)
36
+ FileUtils.mkdir_p(File.join(conf_base, 'behaviour'))
37
+ FileUtils.mkdir_p(File.join(conf_base, 'schemas'))
38
+ # Copy template behaviours (not internal conf/behaviour/ files)
39
+ copy_glob_files(source_root.join('share', 'templates', 'behaviour'),
40
+ File.join(conf_base, 'behaviour'), '*.yml', 'conf/behaviour', copied)
41
+ copy_glob_files(source_root.join('conf', 'schemas'),
42
+ File.join(conf_base, 'schemas'), '*.json', 'conf/schemas', copied)
43
+ copy_if_new(source_root.join('conf', 'adopt_redactions.yml'),
44
+ File.join(conf_base, 'adopt_redactions.yml'),
45
+ copied, 'conf/adopt_redactions.yml')
46
+ end
47
+
48
+ def copy_single_file(source, target, copied, name)
49
+ return if File.exist?(target) || !File.exist?(source)
50
+
51
+ FileUtils.cp(source, target)
52
+ copied << name
53
+ end
54
+
55
+ def copy_executable(source, target, copied, name)
56
+ return unless File.exist?(source)
57
+
58
+ FileUtils.cp(source, target)
59
+ FileUtils.chmod(0o755, target)
60
+ copied << name
61
+ end
62
+
63
+ def copy_directory(source, target, copied, name)
64
+ return unless Dir.exist?(source)
65
+
66
+ FileUtils.cp_r("#{source}/.", target)
67
+ copied << name
68
+ end
69
+
70
+ def copy_glob_files(source_dir, target_dir, pattern, prefix, copied)
71
+ return unless Dir.exist?(source_dir)
72
+
73
+ Dir.glob(File.join(source_dir, pattern)).each do |file|
74
+ basename = File.basename(file)
75
+ target = File.join(target_dir, basename)
76
+
77
+ if File.exist?(target)
78
+ warn " skipped #{prefix}/#{basename} (already exists)"
79
+ next
80
+ end
81
+
82
+ FileUtils.cp(file, target)
83
+ copied << "#{prefix}/#{basename}"
84
+ end
85
+ end
86
+
87
+ def copy_if_new(source, target, copied, name)
88
+ return unless File.exist?(source) && !File.exist?(target)
89
+
90
+ FileUtils.cp(source, target)
91
+ copied << name
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,28 @@
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 Init
8
+ # MCP-facing facade for global structure initialization.
9
+ #
10
+ # Delegates to {DirectoryBuilder} and {FileCopier} for the
11
+ # actual setup logic.
12
+ #
13
+ # @author hugo
14
+ # @author claude
15
+ class GlobalInitializer
16
+ def initialize
17
+ @base_dir = File.join(Dir.home, '.claude')
18
+ end
19
+
20
+ # Sets up the global ~/.claude/ directory structure.
21
+ #
22
+ # @return [Array<String>] created directories
23
+ def setup
24
+ DirectoryBuilder.create_global_structure(@base_dir)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,27 @@
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 Init
8
+ # MCP-facing facade for project-local structure initialization.
9
+ #
10
+ # Delegates to {DirectoryBuilder} for the actual setup logic.
11
+ #
12
+ # @author hugo
13
+ # @author claude
14
+ class LocalInitializer
15
+ def initialize
16
+ @base_dir = File.join(Dir.pwd, '.claude')
17
+ end
18
+
19
+ # Sets up the project-local .claude/ directory structure.
20
+ #
21
+ # @return [Array<String>] created directories
22
+ def setup
23
+ DirectoryBuilder.create_local_structure(@base_dir)
24
+ end
25
+ end
26
+ end
27
+ end