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,180 @@
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 'rubygems/package'
7
+ require 'zlib'
8
+ require 'fileutils'
9
+
10
+ module RosettAi
11
+ module Backup
12
+ # Factory and base for compression handlers
13
+ class Compressor
14
+ ALGORITHMS = ['zip', 'tar.gz', 'tar.xz'].freeze
15
+
16
+ # Returns a compressor instance for the given algorithm.
17
+ #
18
+ # @param algorithm [String] one of 'zip', 'tar.gz', 'tar.xz'
19
+ # @return [Compressor]
20
+ # @raise [RosettAi::BackupError] if algorithm is not supported
21
+ def self.for(algorithm)
22
+ case algorithm
23
+ when 'zip' then ZipCompressor.new
24
+ when 'tar.gz' then TarGzCompressor.new
25
+ when 'tar.xz' then TarXzCompressor.new
26
+ else
27
+ raise RosettAi::BackupError,
28
+ "Unknown compression algorithm: #{algorithm}. Supported: #{ALGORITHMS.join(', ')}"
29
+ end
30
+ end
31
+
32
+ # Whether the compression tool is available on this system.
33
+ #
34
+ # @return [Boolean]
35
+ # @abstract Subclasses must implement this method.
36
+ def available?
37
+ raise NotImplementedError
38
+ end
39
+
40
+ # File extension for archives produced by this compressor.
41
+ #
42
+ # @return [String] file extension including the leading dot
43
+ def extension
44
+ raise NotImplementedError
45
+ end
46
+
47
+ # Compresses files into an archive at +output_path+.
48
+ #
49
+ # @param files [Array<Hash>] entries with :full_path and :archive_path keys
50
+ # @param base_dirs [Array<String>] base directories for relative path resolution
51
+ # @param output_path [String] destination path for the archive
52
+ # @param level [Integer, nil] compression level (algorithm-specific; nil for default)
53
+ # @return [String] the output path
54
+ # @abstract Subclasses must implement this method.
55
+ def compress(files, base_dirs, output_path, level: nil)
56
+ raise NotImplementedError
57
+ end
58
+ end
59
+
60
+ # ZIP compression using rubyzip gem
61
+ class ZipCompressor < Compressor
62
+ # @return [Boolean] true if the rubyzip gem is loadable
63
+ def available?
64
+ require 'zip'
65
+ true
66
+ rescue LoadError
67
+ false
68
+ end
69
+
70
+ # @return [String] ".zip"
71
+ def extension
72
+ '.zip'
73
+ end
74
+
75
+ # @param files [Array<Hash>] entries with :full_path and :archive_path keys
76
+ # @param _base_dirs [Array<String>] unused
77
+ # @param output_path [String] destination path for the archive
78
+ # @param level [Integer, nil] Zlib compression level (nil for default)
79
+ # @return [String] the output path
80
+ def compress(files, _base_dirs, output_path, level: nil)
81
+ require 'zip'
82
+ compression_level = level || Zip.default_compression
83
+
84
+ ::Zip::OutputStream.open(output_path) do |zipfile|
85
+ files.each do |entry|
86
+ zipfile.put_next_entry(entry[:archive_path], nil, nil, ::Zip::Entry::DEFLATED, compression_level)
87
+ zipfile.write(File.read(entry[:full_path]))
88
+ end
89
+ end
90
+
91
+ output_path
92
+ end
93
+ end
94
+
95
+ # tar.gz compression using Ruby stdlib
96
+ class TarGzCompressor < Compressor
97
+ # @return [Boolean] always true (uses Ruby stdlib)
98
+ def available?
99
+ true
100
+ end
101
+
102
+ # @return [String] ".tar.gz"
103
+ def extension
104
+ '.tar.gz'
105
+ end
106
+
107
+ # @param files [Array<Hash>] entries with :full_path and :archive_path keys
108
+ # @param _base_dirs [Array<String>] unused
109
+ # @param output_path [String] destination path for the archive
110
+ # @param level [Integer, nil] Zlib compression level (nil for default)
111
+ # @return [String] the output path
112
+ def compress(files, _base_dirs, output_path, level: nil)
113
+ gz_level = level || Zlib::DEFAULT_COMPRESSION
114
+
115
+ File.open(output_path, 'wb', 0o644) do |file|
116
+ Zlib::GzipWriter.wrap(file, gz_level) do |gz|
117
+ Gem::Package::TarWriter.new(gz) do |tar|
118
+ files.each do |entry|
119
+ content = File.read(entry[:full_path])
120
+ tar.add_file_simple(entry[:archive_path], 0o644, content.bytesize) do |io|
121
+ io.write(content)
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
127
+
128
+ output_path
129
+ end
130
+ end
131
+
132
+ # tar.xz compression using system xz command
133
+ class TarXzCompressor < Compressor
134
+ # @return [Boolean] true if the +xz+ binary is found on PATH
135
+ def available?
136
+ ENV.fetch('PATH', '').split(File::PATH_SEPARATOR).any? do |dir|
137
+ File.executable?(File.join(dir, 'xz'))
138
+ end
139
+ end
140
+
141
+ # @return [String] ".tar.xz"
142
+ def extension
143
+ '.tar.xz'
144
+ end
145
+
146
+ # @param files [Array<Hash>] entries with :full_path and :archive_path keys
147
+ # @param _base_dirs [Array<String>] unused
148
+ # @param output_path [String] destination path for the archive
149
+ # @param level [Integer, nil] xz preset level (1-9; nil for xz default)
150
+ # @return [String] the output path
151
+ # @raise [RosettAi::BackupError] if the xz command fails
152
+ def compress(files, _base_dirs, output_path, level: nil)
153
+ tar_path = "#{output_path}.tar"
154
+
155
+ File.open(tar_path, 'wb', 0o644) do |file|
156
+ Gem::Package::TarWriter.new(file) do |tar|
157
+ files.each do |entry|
158
+ content = File.read(entry[:full_path])
159
+ tar.add_file_simple(entry[:archive_path], 0o644, content.bytesize) do |io|
160
+ io.write(content)
161
+ end
162
+ end
163
+ end
164
+ end
165
+
166
+ xz_args = ['xz']
167
+ xz_args.push("-#{level}") if level
168
+ xz_args.push('--stdout', tar_path)
169
+ success = File.open(output_path, 'wb', 0o644) do |out|
170
+ system(*xz_args, out: out)
171
+ end
172
+ FileUtils.rm_f(tar_path)
173
+
174
+ raise RosettAi::BackupError, 'xz compression failed' unless success
175
+
176
+ output_path
177
+ end
178
+ end
179
+ end
180
+ end
@@ -0,0 +1,91 @@
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 Backup
8
+ # Base class for backup destinations
9
+ class Destination
10
+ SUPPORTED_SCHEMES = ['file', 's3', 'ssh', 'bup', 'https'].freeze
11
+
12
+ attr_reader :uri
13
+
14
+ def initialize(uri)
15
+ @uri = uri
16
+ end
17
+
18
+ # Writes the archive to this destination.
19
+ #
20
+ # @param archive_path [String] path to the archive file to write
21
+ # @return [String] final destination path
22
+ # @abstract Subclasses must implement this method.
23
+ def write(archive_path)
24
+ raise NotImplementedError, "#{self.class}#write not implemented"
25
+ end
26
+
27
+ # Returns a destination instance for the given URI string.
28
+ #
29
+ # @param destination_string [String] URI (e.g. "file:///tmp/backup.zip")
30
+ # @return [Destination] a FileDestination or UnsupportedDestination
31
+ def self.for(destination_string)
32
+ scheme, path = parse_scheme(destination_string)
33
+
34
+ case scheme
35
+ when 'file'
36
+ FileDestination.new(path)
37
+ else
38
+ UnsupportedDestination.new(scheme, path)
39
+ end
40
+ end
41
+
42
+ # Splits a destination string into scheme and path components.
43
+ #
44
+ # @param destination_string [String] URI or bare path
45
+ # @return [Array(String, String)] scheme and path (defaults to "file" scheme)
46
+ def self.parse_scheme(destination_string)
47
+ if destination_string.match?(%r{^[a-z][a-z0-9+\-.]*://})
48
+ scheme, path = destination_string.split('://', 2)
49
+ [scheme, path]
50
+ else
51
+ ['file', destination_string]
52
+ end
53
+ end
54
+ end
55
+
56
+ # Writes archive to local filesystem
57
+ class FileDestination < Destination
58
+ attr_reader :path
59
+
60
+ def initialize(path)
61
+ super("file://#{path}")
62
+ @path = File.expand_path(path)
63
+ end
64
+
65
+ # Copies the archive to the local filesystem destination.
66
+ #
67
+ # @param archive_path [String] path to the archive file to copy
68
+ # @return [String] expanded destination path
69
+ def write(archive_path)
70
+ FileUtils.mkdir_p(File.dirname(@path))
71
+ FileUtils.cp(archive_path, @path)
72
+ @path
73
+ end
74
+ end
75
+
76
+ # Placeholder for unsupported destination schemes
77
+ class UnsupportedDestination < Destination
78
+ attr_reader :scheme
79
+
80
+ def initialize(scheme, path)
81
+ super("#{scheme}://#{path}")
82
+ @scheme = scheme
83
+ end
84
+
85
+ def write(_archive_path)
86
+ raise RosettAi::BackupError,
87
+ "Destination scheme '#{@scheme}' is not yet supported. Supported: file://"
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,156 @@
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 'fileutils'
8
+ require_relative '../yaml_loader'
9
+ require_relative '../validators/schema_validator'
10
+ require_relative '../validators/behaviour_validator'
11
+
12
+ module RosettAi
13
+ module Behaviour
14
+ # MCP-facing facade for behaviour file management.
15
+ #
16
+ # Provides add/modify/delete operations for behaviour YAML files,
17
+ # matching the interface expected by {Mcp::Tools::BehaviourManageTool}.
18
+ #
19
+ # @author hugo
20
+ # @author claude
21
+ class Manager
22
+ # @param behaviour_dir [Pathname, String, nil] override for testing;
23
+ # defaults to XDG user path (~/.config/rosett-ai/conf/behaviour/)
24
+ def initialize(behaviour_dir: nil)
25
+ @behaviour_dir = Pathname.new(behaviour_dir || RosettAi.paths.rai_conf_dir.join('behaviour'))
26
+ end
27
+
28
+ # Creates a new behaviour file.
29
+ #
30
+ # @param name [String] behaviour name
31
+ # @param description [String, nil] behaviour description
32
+ # @return [Pathname] path to created file
33
+ def add(name, description: nil)
34
+ path = behaviour_path(name)
35
+ raise RosettAi::BehaviourError, "Behaviour '#{name}' already exists" if path.exist?
36
+
37
+ data = build_template(name, description || "#{name} behaviour")
38
+ FileUtils.mkdir_p(path.dirname)
39
+ path.write(YAML.dump(data))
40
+ validate_behaviour!(path)
41
+ validate_enforcement!(data)
42
+ path
43
+ end
44
+
45
+ # Modifies an existing behaviour file.
46
+ #
47
+ # @param name [String] behaviour name
48
+ # @param description [String, nil] new description
49
+ # @return [Pathname] path to modified file
50
+ def modify(name, description: nil)
51
+ path = behaviour_path(name)
52
+ raise RosettAi::BehaviourError, "Behaviour '#{name}' not found" unless path.exist?
53
+
54
+ update_behaviour_file(path, description)
55
+ validate_behaviour!(path)
56
+ data = RosettAi::YamlLoader.load_file(path.to_s)
57
+ validate_enforcement!(data)
58
+ path
59
+ end
60
+
61
+ # Deletes a behaviour file.
62
+ #
63
+ # @param name [String] behaviour name
64
+ # @param options [Hash] :force_delete to skip existence check
65
+ # @return [Boolean] true on success
66
+ def delete(name, options: {})
67
+ path = behaviour_path(name)
68
+ force_delete = options.fetch(:force_delete, false)
69
+ raise RosettAi::BehaviourError, "Behaviour '#{name}' not found" unless force_delete || path.exist?
70
+
71
+ path.delete if path.exist?
72
+ cleanup_compiled_rules(name)
73
+ path
74
+ end
75
+
76
+ private
77
+
78
+ def behaviour_path(name)
79
+ @behaviour_dir.join("#{name}.yml")
80
+ end
81
+
82
+ def update_behaviour_file(path, description)
83
+ data = RosettAi::YamlLoader.load_file(path.to_s)
84
+ data['description'] = description if description
85
+ data['modified_at'] = Time.now.utc.strftime('%Y-%m-%d')
86
+ path.write(YAML.dump(data))
87
+ end
88
+
89
+ def cleanup_compiled_rules(name)
90
+ rules_dir = RosettAi.paths.rules_dir
91
+ return unless rules_dir.exist?
92
+
93
+ Dir.glob(rules_dir.join("behaviour-#{name}.*")).each do |compiled|
94
+ next unless managed_compiled_file?(compiled)
95
+
96
+ File.delete(compiled)
97
+ end
98
+ rescue Errno::ENOENT
99
+ # Race condition: file deleted between glob and delete
100
+ end
101
+
102
+ def managed_compiled_file?(path)
103
+ first_line = File.open(path, &:readline)
104
+ first_line.include?('rosett-ai-') && first_line.include?('managed')
105
+ rescue EOFError, Errno::ENOENT
106
+ false
107
+ end
108
+
109
+ def build_template(name, description)
110
+ {
111
+ 'name' => name,
112
+ 'description' => description,
113
+ 'version' => '1.0.0',
114
+ 'author' => 'rosett-ai',
115
+ 'created_at' => Time.now.utc.strftime('%Y-%m-%d'),
116
+ 'modified_at' => Time.now.utc.strftime('%Y-%m-%d'),
117
+ 'modified_by' => 'rosett-ai',
118
+ 'sensitive' => false,
119
+ 'used_in' => [],
120
+ 'rules' => [
121
+ {
122
+ 'id' => 'rule_001',
123
+ 'description' => 'Example rule - replace with actual rule',
124
+ 'priority' => 50,
125
+ 'enabled' => true
126
+ }
127
+ ]
128
+ }
129
+ end
130
+
131
+ def validate_behaviour!(path)
132
+ validator = RosettAi::Validators::BehaviourValidator.new
133
+ return if validator.valid?(path.to_s)
134
+
135
+ # Remove invalid file to prevent leaving bad state
136
+ path.delete if path.exist?
137
+ raise RosettAi::BehaviourError, "Validation failed: #{validator.errors.join(', ')}"
138
+ end
139
+
140
+ # Runs EnforcementValidator on behaviour data. Logs warnings for
141
+ # downgraded rules but does not block the operation (enforcement
142
+ # is optional and downgrades are informational).
143
+ #
144
+ # @param data [Hash] parsed behaviour YAML
145
+ # @return [Hash] validation result
146
+ def validate_enforcement!(data)
147
+ result = RosettAi::Mcp::Enforcement::Validator.new.validate(data)
148
+ result[:downgraded].each do |entry|
149
+ warnings = entry[:warnings].join('; ')
150
+ warn "[rai-enforce] #{entry[:rule_id]} downgraded to advisory: #{warnings}"
151
+ end
152
+ result
153
+ end
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,86 @@
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 Compiler
8
+ # Abstract base class for compiler backends.
9
+ #
10
+ # Backends transform compiled category data into target-specific output
11
+ # formats. Each backend is paired with a TargetProfile loaded from
12
+ # conf/engines/<name>/target.yml.
13
+ #
14
+ # Factory method .for(target_name) loads the profile and resolves the
15
+ # correct backend class from the Plugins::Registry.
16
+ class Backend
17
+ # Built-in backends that don't require an external engine gem.
18
+ # Lazy-loaded via lambdas to avoid circular requires.
19
+ BUILTIN_BACKENDS = {
20
+ 'agents_md' => -> { RosettAi::Compiler::Backends::AgentsMdBackend },
21
+ 'claude' => -> { RosettAi::Compiler::Backends::ClaudeBackend }
22
+ }.freeze
23
+
24
+ attr_reader :profile
25
+
26
+ def initialize(profile)
27
+ @profile = profile
28
+ end
29
+
30
+ # Factory: load a target profile and instantiate the appropriate backend.
31
+ #
32
+ # @param target_name [String, Symbol] engine/target identifier
33
+ # @return [Backend] the engine-specific backend instance
34
+ # @raise [RosettAi::CompileError] if the target or backend is unknown
35
+ def self.for(target_name)
36
+ name = target_name.to_s
37
+ profile = TargetProfile.load(name)
38
+ resolve_backend_class(profile.backend).new(profile)
39
+ end
40
+
41
+ def self.resolve_backend_class(backend_name)
42
+ return BUILTIN_BACKENDS[backend_name].call if BUILTIN_BACKENDS.key?(backend_name)
43
+
44
+ unless Plugins::Registry.registered?(:engine, backend_name)
45
+ available = Plugins::Registry.available(:engine)
46
+ hint = if available.empty?
47
+ "No engine plugins installed. Install with: apt install rosett-ai-engine-#{backend_name.tr('_', '-')}"
48
+ else
49
+ "Available engines: #{available.join(', ')}"
50
+ end
51
+ raise CompileError,
52
+ "Unknown backend: #{backend_name}. #{hint}"
53
+ end
54
+
55
+ plugin = Plugins::Registry.plugin_module(:engine, backend_name)
56
+ plugin.backend_class
57
+ end
58
+ private_class_method :resolve_backend_class
59
+
60
+ def render(_data)
61
+ raise NotImplementedError, "#{self.class}#render must be implemented"
62
+ end
63
+
64
+ def generated_marker
65
+ raise NotImplementedError, "#{self.class}#generated_marker must be implemented"
66
+ end
67
+
68
+ def managed_file?(path)
69
+ return false unless File.exist?(path)
70
+
71
+ first_line = File.open(path, &:readline)
72
+ first_line.start_with?(generated_marker)
73
+ rescue EOFError
74
+ false
75
+ end
76
+
77
+ def file_extension
78
+ case profile.output_format
79
+ when 'json' then '.json'
80
+ when 'yaml' then '.yml'
81
+ else '.md'
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,80 @@
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 Compiler
8
+ module Backends
9
+ # Compiler backend for AGENTS.md (AAIF standard format).
10
+ #
11
+ # Renders behaviour configuration into idiomatic AGENTS.md files
12
+ # compatible with Claude Code, Goose, Cursor, and other AAIF-aligned tools.
13
+ # Output is valid Markdown without YAML frontmatter, following the
14
+ # AGENTS.md v0.1.0 specification.
15
+ #
16
+ # @author hugo
17
+ # @author claude
18
+ class AgentsMdBackend < Backend
19
+ MARKER_PREFIX = '<!-- rosett-ai-agents-md-managed'
20
+
21
+ # Renders behaviour data into AGENTS.md format.
22
+ #
23
+ # @param data [Hash] parsed YAML behaviour data
24
+ # @return [String] rendered Markdown content
25
+ def render(data)
26
+ lines = build_header(data)
27
+ lines.concat(build_description(data))
28
+ lines.concat(build_rules(data))
29
+ lines << ''
30
+ lines.join("\n")
31
+ end
32
+
33
+ # @return [String] marker prefix for managed file detection
34
+ def generated_marker
35
+ MARKER_PREFIX
36
+ end
37
+
38
+ private
39
+
40
+ def build_header(data)
41
+ lines = ["#{MARKER_PREFIX} -->"]
42
+ if profile.rendering.fetch('include_source_metadata', false)
43
+ lines << "<!-- Source: conf/#{data['category']}/#{data['name']}.yml -->"
44
+ end
45
+ lines << ''
46
+ lines << "# #{format_title(data['name'])}"
47
+ lines
48
+ end
49
+
50
+ def build_description(data)
51
+ desc = data['description'].to_s.strip
52
+ return [] if desc.empty?
53
+
54
+ ['', desc]
55
+ end
56
+
57
+ def build_rules(data)
58
+ rules = enabled_rules(data)
59
+ return [] if rules.empty?
60
+
61
+ lines = ['', '## Rules', '']
62
+ rules.each do |rule|
63
+ lines << "- #{rule['description'].to_s.strip}"
64
+ end
65
+ lines
66
+ end
67
+
68
+ def enabled_rules(data)
69
+ (data['rules'] || [])
70
+ .select { |r| r.fetch('enabled', true) }
71
+ .sort_by { |r| -(r['priority'] || 50) }
72
+ end
73
+
74
+ def format_title(name)
75
+ name.to_s.tr('_', ' ').gsub(/\b\w/, &:upcase)
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,88 @@
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 Compiler
8
+ module Backends
9
+ # Compiler backend for Claude Code rule files.
10
+ #
11
+ # Renders behaviour configuration into Markdown files for Claude Code's
12
+ # ~/.claude/rules/ directory. Output includes priority annotations,
13
+ # source metadata, and the rosett-ai-claude-managed marker for idempotent
14
+ # updates.
15
+ class ClaudeBackend < Backend
16
+ MARKER_PREFIX = '<!-- rosett-ai-claude-managed'
17
+
18
+ # Renders behaviour data into Claude Code rule format.
19
+ #
20
+ # @param data [Hash] parsed YAML behaviour data
21
+ # @return [String] rendered Markdown content
22
+ def render(data)
23
+ sections = [
24
+ build_header(data),
25
+ build_description(data),
26
+ build_rules(data),
27
+ ['']
28
+ ]
29
+ sections.flatten.join("\n")
30
+ end
31
+
32
+ # @return [String] marker prefix for managed file detection
33
+ def generated_marker
34
+ MARKER_PREFIX
35
+ end
36
+
37
+ private
38
+
39
+ def build_header(data)
40
+ header = ["#{MARKER_PREFIX} -->"]
41
+ append_source_metadata(header, data)
42
+ header << ''
43
+ header << "# #{data['name']}"
44
+ end
45
+
46
+ def append_source_metadata(header, data)
47
+ return unless profile.rendering.fetch('include_source_metadata', false)
48
+
49
+ header << "<!-- Source: conf/#{data['category']}/#{data['name']}.yml -->"
50
+ header << "<!-- Category: #{data['category']} | Version: #{data['version']} -->"
51
+ end
52
+
53
+ def build_description(data)
54
+ desc = data['description'].to_s.strip
55
+ return [] if desc.empty?
56
+
57
+ ['', desc]
58
+ end
59
+
60
+ def build_rules(data)
61
+ rules = enabled_rules(data)
62
+ return [] if rules.empty?
63
+
64
+ ['', '## Rules'] + rules.flat_map { |rule| format_rule(rule) }
65
+ end
66
+
67
+ def format_rule(rule)
68
+ heading = if priority_annotations?
69
+ "### #{rule['id']} (priority: #{rule['priority'] || 50})"
70
+ else
71
+ "### #{rule['id']}"
72
+ end
73
+ ['', heading, '', rule['description'].to_s.strip]
74
+ end
75
+
76
+ def priority_annotations?
77
+ profile.rendering.fetch('include_priority_annotations', false)
78
+ end
79
+
80
+ def enabled_rules(data)
81
+ (data['rules'] || [])
82
+ .select { |r| r.fetch('enabled', true) }
83
+ .sort_by { |r| -(r['priority'] || 50) }
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,15 @@
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 Compiler
8
+ # Compiler backend adapters.
9
+ module Backends
10
+ # Deprecated — use RosettAiEngine::Generic::Backend. Will be removed in v1.1.0.
11
+ # Only defined when rosett-ai-engine-generic gem is installed.
12
+ GenericBackend = RosettAiEngine::Generic::Backend if defined?(RosettAiEngine::Generic::Backend)
13
+ end
14
+ end
15
+ end