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,59 @@
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 Doctor
8
+ module Checks
9
+ # Warns when a stale .rosett-ai/ directory exists at $HOME level.
10
+ #
11
+ # Having .rosett-ai/ at $HOME is almost never intentional — it is typically
12
+ # left over from a previous `rai init --project` run at the home
13
+ # directory. It causes scope resolution confusion because behaviour
14
+ # files in ~/.rosett-ai/conf/behaviour/ shadow XDG-path files.
15
+ #
16
+ # @author hugo
17
+ # @author claude
18
+ class StaleHomeNnccCheck
19
+ include Check
20
+
21
+ check_name 'stale_home_nncc'
22
+
23
+ private
24
+
25
+ def perform
26
+ home_rai = Pathname.new(Dir.home).join('.rosett-ai')
27
+
28
+ unless home_rai.directory?
29
+ pass!(::I18n.t('rosett_ai.doctor.stale_home_nncc.clean'))
30
+ return
31
+ end
32
+
33
+ warn_stale(home_rai)
34
+ end
35
+
36
+ def warn_stale(home_rai)
37
+ path_str = home_rai.to_s
38
+ removal = ::I18n.t('rosett_ai.doctor.stale_home_nncc.remove', path: path_str)
39
+ behaviour_dir = home_rai.join('conf', 'behaviour')
40
+ children = behaviour_dir.directory? ? behaviour_dir.children : []
41
+
42
+ if children.any?
43
+ warn!(
44
+ ::I18n.t('rosett_ai.doctor.stale_home_nncc.found', path: path_str, count: children.count),
45
+ remediation: removal
46
+ )
47
+ else
48
+ warn!(
49
+ ::I18n.t('rosett_ai.doctor.stale_home_nncc.empty', path: path_str),
50
+ remediation: removal
51
+ )
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ RosettAi::Doctor.register(RosettAi::Doctor::Checks::StaleHomeNnccCheck)
@@ -0,0 +1,81 @@
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
+ # Self-diagnostic runner for Rosett-AI runtime prerequisites.
8
+ #
9
+ # Orchestrates registered checks and collects results.
10
+ # Each check implements the Check interface (#name, #run, #status,
11
+ # #message, #remediation).
12
+ #
13
+ # @author hugo
14
+ # @author claude
15
+ # @see conf/design/doctor.yml
16
+ module Doctor
17
+ STATUSES = ['pass', 'warn', 'fail'].freeze
18
+
19
+ @checks = []
20
+ @mutex = Mutex.new
21
+
22
+ class << self
23
+ # Register a check class.
24
+ #
25
+ # @param check_class [Class] a class that includes Doctor::Check
26
+ # @return [void]
27
+ def register(check_class)
28
+ @mutex.synchronize { @checks << check_class }
29
+ end
30
+
31
+ # Return all registered check classes.
32
+ #
33
+ # @return [Array<Class>]
34
+ def checks
35
+ @mutex.synchronize { @checks.dup }
36
+ end
37
+
38
+ # Return the names of all registered checks.
39
+ #
40
+ # @return [Array<String>]
41
+ def check_names
42
+ checks.map(&:check_name)
43
+ end
44
+
45
+ # Run all registered checks (or a filtered subset).
46
+ #
47
+ # @param only [String, nil] run only the named check
48
+ # @return [Array<Hash>] results with :name, :status, :message, :remediation
49
+ def run_all(only: nil)
50
+ targets = only ? checks.select { |klass| klass.check_name == only } : checks
51
+ targets.map do |check_class|
52
+ instance = check_class.new
53
+ instance.run
54
+ {
55
+ name: instance.name,
56
+ status: instance.status.to_s,
57
+ message: instance.message,
58
+ remediation: instance.remediation
59
+ }
60
+ end
61
+ end
62
+
63
+ # Determine the overall exit code from results.
64
+ #
65
+ # @param results [Array<Hash>] check results
66
+ # @return [Integer] 0=all pass, 1=any warn, 2=any fail
67
+ def exit_code(results)
68
+ return 2 if results.any? { |r| r[:status] == 'fail' }
69
+ return 1 if results.any? { |r| r[:status] == 'warn' }
70
+
71
+ 0
72
+ end
73
+
74
+ # Reset registered checks. Intended for test isolation.
75
+ # @return [void]
76
+ def reset!
77
+ @mutex.synchronize { @checks = [] }
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,122 @@
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
+ require 'pathname'
8
+
9
+ module RosettAi
10
+ module Documentation
11
+ # Compiles the LaTeX technical reference document.
12
+ #
13
+ # Runs the full pdflatex → biber → makeindex → pdflatex toolchain
14
+ # to produce doc/reference/rosett-ai-technical-reference.pdf from the
15
+ # LaTeX sources in doc/reference/src/.
16
+ #
17
+ # Design reference: conf/design/documentation.yml
18
+ class ReferenceCompiler
19
+ REQUIRED_TOOLS = ['pdflatex', 'biber', 'makeindex'].freeze
20
+ OUTPUT_NAME = 'rosett-ai-technical-reference.pdf'
21
+ BUILD_ARTIFACTS = ['aux', 'bbl', 'bcf', 'blg', 'idx', 'ilg', 'ind',
22
+ 'lof', 'log', 'lot', 'out', 'ptc', 'run.xml', 'toc'].freeze
23
+
24
+ def initialize(root: nil)
25
+ @root = root || RosettAi.root
26
+ end
27
+
28
+ def available?
29
+ REQUIRED_TOOLS.all? { |tool| tool_on_path?(tool) }
30
+ end
31
+
32
+ def missing_tools
33
+ REQUIRED_TOOLS.reject { |tool| tool_on_path?(tool) }
34
+ end
35
+
36
+ def stale?
37
+ pdf = output_path
38
+ return true unless pdf.exist?
39
+
40
+ pdf_mtime = pdf.mtime
41
+ source_files.any? { |f| f.mtime > pdf_mtime }
42
+ end
43
+
44
+ def compile!
45
+ raise RosettAi::DocumentationError, "Missing LaTeX tools: #{missing_tools.join(', ')}" unless available?
46
+ raise RosettAi::DocumentationError, "Source directory not found: #{source_dir}" unless source_dir.exist?
47
+
48
+ Dir.chdir(source_dir) do
49
+ run_pdflatex!
50
+ run_biber!
51
+ run_makeindex
52
+ 3.times { run_pdflatex! }
53
+ end
54
+
55
+ install_pdf!
56
+ clean_artifacts!
57
+ end
58
+
59
+ def output_path
60
+ @root.join('doc', 'reference', OUTPUT_NAME)
61
+ end
62
+
63
+ def source_dir
64
+ @root.join('doc', 'reference', 'src')
65
+ end
66
+
67
+ private
68
+
69
+ def source_files
70
+ patterns = [
71
+ @root.join('conf', 'design', '*.yml'),
72
+ @root.join('conf', 'schemas', '*.json'),
73
+ source_dir.join('*.tex'),
74
+ source_dir.join('*.bib'),
75
+ source_dir.join('*.ist')
76
+ ]
77
+ patterns.flat_map { |pat| Dir.glob(pat).map { |f| Pathname.new(f) } }
78
+ end
79
+
80
+ def run_pdflatex!
81
+ success = system('pdflatex', '-interaction=nonstopmode', 'main.tex',
82
+ [:out, :err] => File::NULL)
83
+ raise RosettAi::DocumentationError, 'pdflatex compilation failed' unless success
84
+ end
85
+
86
+ def run_biber!
87
+ success = system('biber', 'main', [:out, :err] => File::NULL)
88
+ raise RosettAi::DocumentationError, 'biber bibliography processing failed' unless success
89
+ end
90
+
91
+ # makeindex failure is non-fatal — the document compiles without an index
92
+ def run_makeindex
93
+ idx_file = source_dir.join('main.idx')
94
+ return unless idx_file.exist?
95
+
96
+ system('makeindex', 'main.idx', '-s', 'StyleInd.ist',
97
+ [:out, :err] => File::NULL)
98
+ end
99
+
100
+ def install_pdf!
101
+ built_pdf = source_dir.join('main.pdf')
102
+ raise RosettAi::DocumentationError, 'LaTeX compilation produced no PDF' unless built_pdf.exist?
103
+
104
+ FileUtils.mkdir_p(output_path.dirname)
105
+ FileUtils.cp(built_pdf.to_s, output_path.to_s)
106
+ end
107
+
108
+ def clean_artifacts!
109
+ BUILD_ARTIFACTS.each do |ext|
110
+ FileUtils.rm_f(source_dir.join("main.#{ext}").to_s)
111
+ end
112
+ FileUtils.rm_f(source_dir.join('main.pdf').to_s)
113
+ end
114
+
115
+ def tool_on_path?(name)
116
+ ENV.fetch('PATH', '').split(File::PATH_SEPARATOR).any? do |dir|
117
+ File.executable?(File.join(dir, name))
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,62 @@
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 Documentation
10
+ # Produces draft translations of documentation files using the Claude API.
11
+ #
12
+ # Reads source documentation, sends to API with a translation prompt,
13
+ # and writes the result to doc/locales/{locale}/.
14
+ class Translator
15
+ MODEL = 'claude-sonnet-4-20250514'
16
+
17
+ def initialize(doc_dir: nil)
18
+ @doc_dir = doc_dir || RosettAi.root.join('doc')
19
+ end
20
+
21
+ def translate(source_file, from:, to:)
22
+ content = File.read(source_file)
23
+ basename = File.basename(source_file)
24
+
25
+ translated = call_api(content, from: from, to: to)
26
+
27
+ output_dir = @doc_dir.join('locales', to)
28
+ FileUtils.mkdir_p(output_dir)
29
+ output_path = output_dir.join(basename)
30
+ File.open(output_path, 'w', 0o644) { |f| f.write(translated) }
31
+
32
+ output_path.to_s
33
+ end
34
+
35
+ private
36
+
37
+ def call_api(content, from:, to:)
38
+ require 'anthropic' # lazy require — gem not in core deps
39
+ client = Anthropic::Client.new
40
+ response = client.messages.create(
41
+ model: MODEL,
42
+ max_tokens: 4096,
43
+ messages: [{ role: 'user', content: build_prompt(content, from: from, to: to) }]
44
+ )
45
+
46
+ response.content.first.text
47
+ end
48
+
49
+ def build_prompt(content, from:, to:)
50
+ <<~PROMPT
51
+ Translate the following documentation from #{from} to #{to}.
52
+ Preserve all markdown formatting, code blocks, and technical terms.
53
+ Do not translate code examples, file paths, or command names.
54
+ Return only the translated document, no explanations.
55
+
56
+ ---
57
+ #{content}
58
+ PROMPT
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,203 @@
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 'json'
7
+ require 'json_schemer'
8
+ require 'digest'
9
+
10
+ module RosettAi
11
+ module Engines
12
+ # Abstract base class for engine config compilers.
13
+ #
14
+ # Extracts shared compilation infrastructure from Claude::ConfigCompiler:
15
+ # file discovery, YAML loading, schema validation, checksum diffing,
16
+ # action determination, and result writing.
17
+ #
18
+ # Subclasses must implement:
19
+ # - #config_dir → Pathname to scope YAML directory
20
+ # - #schema_path → Pathname to JSON Schema file
21
+ # - #scope_router → object responding to #target_path(scope)
22
+ # - #transform(data, scope) → [json_data, warnings]
23
+ # - #output_format → Symbol (:json, :yaml)
24
+ class BaseConfigCompiler
25
+ HEADER_KEYS = ['name', 'scope', 'version', 'status', 'compatibility',
26
+ 'author', 'created_at', 'modified_at', 'modified_by'].freeze
27
+
28
+ def initialize(simulate: false, secret_resolver: nil)
29
+ @simulate = simulate
30
+ @secret_resolver = secret_resolver || RosettAi::Config::SecretResolver.new
31
+ @schema = load_schema
32
+ end
33
+
34
+ # Compile all scope files. Returns [RosettAi::Config::CompileResult].
35
+ def compile
36
+ discover_scope_files.map { |path| compile_scope(path) }
37
+ end
38
+
39
+ # Compile a single scope file. Returns RosettAi::Config::CompileResult.
40
+ def compile_scope(path)
41
+ warnings = []
42
+ data = load_and_validate(path)
43
+ header = extract_header(data)
44
+ scope = header['scope']
45
+ warnings.concat(check_compatibility(header, path))
46
+
47
+ json_data, transform_warnings = transform(data, scope)
48
+ warnings.concat(transform_warnings)
49
+
50
+ json_data = @secret_resolver.resolve_all(json_data)
51
+
52
+ target = scope_router.target_path(scope)
53
+ action, diff = determine_action(target, json_data)
54
+
55
+ RosettAi::Config::CompileResult.new(
56
+ scope: scope, source_path: Pathname.new(path), target_path: target,
57
+ json_data: json_data, warnings: warnings, action: action, diff: diff
58
+ )
59
+ rescue RosettAi::Error, ArgumentError, JSON::Schema::ValidationError => e
60
+ RosettAi::Config::CompileResult.new(
61
+ scope: extract_scope_from_path(path), source_path: Pathname.new(path),
62
+ target_path: nil, json_data: nil,
63
+ warnings: [e.message], action: :skipped, diff: nil
64
+ )
65
+ end
66
+
67
+ # Discover scope files in the engine's config directory.
68
+ def discover_scope_files
69
+ dir = config_dir
70
+ return [] unless dir.exist?
71
+
72
+ Dir.glob(dir.join('*.yml'))
73
+ end
74
+
75
+ # Write compiled results to their target paths.
76
+ def write_results(results)
77
+ return results if @simulate
78
+
79
+ results.each do |result|
80
+ next unless result.changed? && result.target_path
81
+
82
+ write_output(result.target_path, result.json_data)
83
+ end
84
+ results
85
+ end
86
+
87
+ protected
88
+
89
+ # Subclass must override: path to scope YAML directory.
90
+ def config_dir
91
+ raise NotImplementedError, "#{self.class}#config_dir must be implemented"
92
+ end
93
+
94
+ # Subclass must override: path to JSON Schema file.
95
+ def schema_path
96
+ raise NotImplementedError, "#{self.class}#schema_path must be implemented"
97
+ end
98
+
99
+ # Subclass must override: scope router instance.
100
+ def scope_router
101
+ raise NotImplementedError, "#{self.class}#scope_router must be implemented"
102
+ end
103
+
104
+ # Subclass must override: transform data into output format.
105
+ # @return [Array(Hash, Array<String>)] [output_data, warnings]
106
+ def transform(_data, _scope)
107
+ raise NotImplementedError, "#{self.class}#transform must be implemented"
108
+ end
109
+
110
+ # Subclass may override: output format (:json or :yaml).
111
+ def output_format = :json
112
+
113
+ # Subclass may override: compatibility checks.
114
+ def check_compatibility(_header, _path)
115
+ []
116
+ end
117
+
118
+ private
119
+
120
+ def load_schema
121
+ schema_data = JSON.parse(schema_path.read)
122
+ JSONSchemer.schema(schema_data)
123
+ end
124
+
125
+ def load_and_validate(path)
126
+ data = RosettAi::YamlLoader.load_file(path)
127
+ errors = @schema.validate(data).to_a
128
+ return data if errors.empty?
129
+
130
+ messages = errors.map do |err|
131
+ pointer = err['data_pointer'].empty? ? 'root' : err['data_pointer']
132
+ "#{pointer}: #{err['type']}"
133
+ end
134
+ raise RosettAi::ValidationError, "Schema validation failed for #{path}: #{messages.join(', ')}"
135
+ end
136
+
137
+ def extract_header(data)
138
+ HEADER_KEYS.each_with_object({}) do |key, header|
139
+ header[key] = data[key] if data.key?(key)
140
+ end
141
+ end
142
+
143
+ def determine_action(target_path, output_data)
144
+ return [:created, nil] unless target_path.exist?
145
+
146
+ existing = File.read(target_path)
147
+ new_content = serialize(output_data)
148
+
149
+ if checksum(existing) == checksum(new_content)
150
+ [:unchanged, nil]
151
+ else
152
+ diff = @simulate ? generate_diff(existing, new_content) : nil
153
+ [:updated, diff]
154
+ end
155
+ rescue Errno::ENOENT
156
+ [:created, nil]
157
+ end
158
+
159
+ def serialize(data)
160
+ case output_format
161
+ when :yaml then YAML.dump(data)
162
+ else JSON.pretty_generate(data)
163
+ end
164
+ end
165
+
166
+ def generate_diff(old_content, new_content)
167
+ old_lines = old_content.lines
168
+ new_lines = new_content.lines
169
+
170
+ lines = []
171
+ lines << '--- a/settings'
172
+ lines << '+++ b/settings'
173
+
174
+ max = [old_lines.size, new_lines.size].max
175
+ (0...max).each do |i|
176
+ old_line = old_lines[i]
177
+ new_line = new_lines[i]
178
+ next if old_line == new_line
179
+
180
+ lines << "@@ -#{i + 1} +#{i + 1} @@"
181
+ lines << "-#{old_line&.chomp}" if old_line
182
+ lines << "+#{new_line&.chomp}" if new_line
183
+ end
184
+
185
+ lines.join("\n")
186
+ end
187
+
188
+ def checksum(content)
189
+ Digest::SHA256.hexdigest(content)
190
+ end
191
+
192
+ def write_output(path, data)
193
+ FileUtils.mkdir_p(path.dirname)
194
+ content = serialize(data)
195
+ File.open(path, 'w', 0o644) { |f| f.write(content) }
196
+ end
197
+
198
+ def extract_scope_from_path(path)
199
+ File.basename(path, '.yml')
200
+ end
201
+ end
202
+ end
203
+ end
@@ -0,0 +1,63 @@
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 Engines
8
+ # Base engine detector that reads manifest detection rules and probes the system.
9
+ #
10
+ # Subclasses may override `custom_checks` to add engine-specific detection logic
11
+ # beyond the manifest-driven cli_binary and config_dir checks.
12
+ class Detector
13
+ attr_reader :engine_name
14
+
15
+ def initialize(engine_name)
16
+ @engine_name = engine_name.to_s
17
+ @manifest = Registry.manifest(@engine_name)
18
+ end
19
+
20
+ def detect
21
+ checks = []
22
+ detection = @manifest['detection'] || {}
23
+
24
+ checks << check_cli_binary(detection['cli_binary']) if detection['cli_binary']
25
+ checks << check_config_dir(detection['config_dir']) if detection['config_dir']
26
+ checks.concat(custom_checks)
27
+
28
+ {
29
+ engine: @engine_name,
30
+ display_name: @manifest['display_name'],
31
+ checks: checks,
32
+ detected: checks.all? { |check| check[:passed] }
33
+ }
34
+ end
35
+
36
+ protected
37
+
38
+ def custom_checks
39
+ []
40
+ end
41
+
42
+ private
43
+
44
+ def check_cli_binary(binary_name)
45
+ found = system('which', binary_name, out: File::NULL, err: File::NULL)
46
+ {
47
+ type: :cli_binary,
48
+ name: binary_name,
49
+ passed: found == true
50
+ }
51
+ end
52
+
53
+ def check_config_dir(dir_path)
54
+ expanded = File.expand_path(dir_path)
55
+ {
56
+ type: :config_dir,
57
+ name: dir_path,
58
+ passed: Dir.exist?(expanded)
59
+ }
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,50 @@
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 'json_schemer'
7
+
8
+ module RosettAi
9
+ module Engines
10
+ # Engine registry — delegates to RosettAi::Plugins::Registry for dynamic
11
+ # plugin discovery. External engine gems self-register via their
12
+ # register.rb files, discovered by Gem.find_files.
13
+ class Registry
14
+ class << self
15
+ def available
16
+ RosettAi::Plugins::Registry.available(:engine)
17
+ end
18
+
19
+ def engine_module(name)
20
+ RosettAi::Plugins::Registry.plugin_module(:engine, name.to_s)
21
+ end
22
+
23
+ def manifest(name)
24
+ mod = engine_module(name)
25
+ path = mod.manifest_path
26
+ raise RosettAi::Error, "No manifest for engine: #{name}" unless path.exist?
27
+
28
+ data = RosettAi::YamlLoader.load_file(path)
29
+ validate_manifest!(data, path)
30
+ data
31
+ end
32
+
33
+ private
34
+
35
+ def validate_manifest!(data, path)
36
+ schema_path = RosettAi.root.join('conf', 'schemas', 'engine_manifest_schema.json')
37
+ schema = JSON.parse(schema_path.read)
38
+ schemer = JSONSchemer.schema(schema)
39
+ errors = schemer.validate(data).to_a
40
+
41
+ return if errors.empty?
42
+
43
+ messages = errors.map { |e| "#{e['data_pointer']}: #{e['type']}" }
44
+ raise RosettAi::Error,
45
+ "Invalid engine manifest at #{path}: #{messages.join(', ')}"
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end