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,44 @@
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 AiConfig
8
+ # Advisory cost tier preferences for AI tool usage.
9
+ #
10
+ # Cost controls are purely advisory — rosett-ai is a compiler, not a runtime.
11
+ # These preferences guide model selection and are compiled into
12
+ # engine-specific configuration hints.
13
+ class CostControls
14
+ TIERS = ['economy', 'standard', 'premium'].freeze
15
+
16
+ attr_reader :preferred_tier, :monthly_budget_note
17
+
18
+ # @param preferred_tier [String] one of {TIERS}
19
+ # @param monthly_budget_note [String, nil] advisory budget note
20
+ def initialize(preferred_tier: 'standard', monthly_budget_note: nil)
21
+ validate_tier!(preferred_tier)
22
+
23
+ @preferred_tier = preferred_tier.freeze
24
+ @monthly_budget_note = monthly_budget_note&.freeze
25
+ end
26
+
27
+ # @return [Hash] serializable representation
28
+ def to_h
29
+ hash = { 'preferred_tier' => @preferred_tier }
30
+ hash['monthly_budget_note'] = @monthly_budget_note if @monthly_budget_note
31
+ hash
32
+ end
33
+
34
+ private
35
+
36
+ def validate_tier!(tier)
37
+ return if TIERS.include?(tier)
38
+
39
+ raise ArgumentError,
40
+ "Invalid cost tier '#{tier}'. Allowed: #{TIERS.join(', ')}"
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,64 @@
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 AiConfig
8
+ # Ordered engine fallback chain with local-to-remote transition warnings.
9
+ #
10
+ # When the primary engine is unavailable, the chain specifies fallback
11
+ # engines in order. Warns when a fallback would switch from local to
12
+ # remote (network requirement change).
13
+ class FallbackChain
14
+ LOCAL_ENGINES = ['ollama', 'gpt_neox'].freeze
15
+
16
+ attr_reader :engines, :warnings
17
+
18
+ # @param engines [Array<String>] ordered engine identifiers
19
+ def initialize(engines: [])
20
+ @engines = engines.freeze
21
+ @warnings = detect_transition_warnings.freeze
22
+ end
23
+
24
+ # @return [String, nil] primary engine (first in chain)
25
+ def primary
26
+ @engines.first
27
+ end
28
+
29
+ # @return [Array<String>] fallback engines (all after primary)
30
+ def fallbacks
31
+ @engines.drop(1)
32
+ end
33
+
34
+ # @return [Boolean] true if chain contains local-to-remote transitions
35
+ def local_to_remote_transition?
36
+ !@warnings.empty?
37
+ end
38
+
39
+ # @return [Hash] serializable representation
40
+ def to_h
41
+ {
42
+ 'engines' => @engines.dup,
43
+ 'warnings' => @warnings.dup
44
+ }
45
+ end
46
+
47
+ private
48
+
49
+ def detect_transition_warnings
50
+ warnings = []
51
+ @engines.each_cons(2) do |current, fallback|
52
+ next unless local_engine?(current) && !local_engine?(fallback)
53
+
54
+ warnings << "Fallback from local (#{current}) to remote (#{fallback}) — network required"
55
+ end
56
+ warnings
57
+ end
58
+
59
+ def local_engine?(engine)
60
+ LOCAL_ENGINES.include?(engine)
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,121 @@
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 AiConfig
8
+ # Maps canonical model tiers to engine-specific model identifiers.
9
+ #
10
+ # Supports three standard tiers (economy, standard, premium) plus
11
+ # custom model names. Engine manifests declare their model mappings.
12
+ # Use {.with_manifest_mappings} to load mappings from installed engines.
13
+ #
14
+ # @author hugo
15
+ # @author claude
16
+ class ModelRouter
17
+ CANONICAL_TIERS = ['economy', 'standard', 'premium'].freeze
18
+
19
+ # Default model mappings per engine (used when engine manifest unavailable).
20
+ DEFAULT_MAPPINGS = {
21
+ 'claude' => {
22
+ 'economy' => 'claude-haiku-4-5-20251001',
23
+ 'standard' => 'claude-sonnet-4-5-20250929',
24
+ 'premium' => 'claude-opus-4-6'
25
+ },
26
+ 'ollama' => {
27
+ 'economy' => 'llama3.2:3b',
28
+ 'standard' => 'llama3.3:8b',
29
+ 'premium' => 'llama3.3:70b'
30
+ }
31
+ }.freeze
32
+
33
+ # Builds a ModelRouter that merges manifest-declared model
34
+ # mappings from installed engines with DEFAULT_MAPPINGS.
35
+ #
36
+ # @param extra_mappings [Hash] additional overrides on top of manifest data
37
+ # @return [ModelRouter]
38
+ def self.with_manifest_mappings(extra_mappings: {})
39
+ manifest_mappings = load_manifest_mappings
40
+ new(custom_mappings: manifest_mappings.merge(extra_mappings))
41
+ end
42
+
43
+ # @param custom_mappings [Hash] additional engine model mappings
44
+ def initialize(custom_mappings: {})
45
+ @mappings = DEFAULT_MAPPINGS.merge(custom_mappings)
46
+ end
47
+
48
+ # Resolves a canonical tier to an engine-specific model ID.
49
+ #
50
+ # @param tier [String] canonical tier name or "custom:<name>"
51
+ # @param engine [String] engine identifier
52
+ # @return [String] resolved model identifier
53
+ # @raise [RosettAi::AiConfigError] if tier is not recognised
54
+ def resolve(tier, engine:)
55
+ return extract_custom_model(tier) if tier.start_with?('custom:')
56
+
57
+ validate_tier!(tier)
58
+ engine_map = @mappings.fetch(engine, {})
59
+ engine_map.fetch(tier) do
60
+ raise RosettAi::AiConfigError,
61
+ "No model mapping for tier '#{tier}' on engine '#{engine}'"
62
+ end
63
+ end
64
+
65
+ # Lists all available tiers for an engine.
66
+ #
67
+ # @param engine [String] engine identifier
68
+ # @return [Array<String>] available tier names
69
+ def available_tiers(engine)
70
+ engine_map = @mappings.fetch(engine, {})
71
+ engine_map.keys
72
+ end
73
+
74
+ # @param engine [String] engine identifier
75
+ # @return [Boolean] true if engine has model mappings
76
+ def engine_supported?(engine)
77
+ @mappings.key?(engine)
78
+ end
79
+
80
+ # Resolves a canonical tier across all known engines.
81
+ #
82
+ # @param tier [String] canonical tier name
83
+ # @return [Hash<String, String>] engine name => resolved model ID
84
+ def resolve_all(tier)
85
+ validate_tier!(tier)
86
+ @mappings.each_with_object({}) do |(engine, map), result|
87
+ result[engine] = map[tier] if map.key?(tier)
88
+ end
89
+ end
90
+
91
+ private
92
+
93
+ def validate_tier!(tier)
94
+ return if CANONICAL_TIERS.include?(tier)
95
+
96
+ raise RosettAi::AiConfigError,
97
+ "Invalid model tier '#{tier}'. Allowed: #{CANONICAL_TIERS.join(', ')}, or custom:<name>"
98
+ end
99
+
100
+ def extract_custom_model(tier)
101
+ tier.delete_prefix('custom:')
102
+ end
103
+
104
+ class << self
105
+ private
106
+
107
+ def load_manifest_mappings
108
+ mappings = {}
109
+ RosettAi::Engines::Registry.available.each do |engine_name|
110
+ manifest = RosettAi::Engines::Registry.manifest(engine_name)
111
+ routing = manifest['model_routing']
112
+ mappings[engine_name] = routing if routing.is_a?(Hash)
113
+ rescue RosettAi::Error
114
+ next
115
+ end
116
+ mappings
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,45 @@
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
+
9
+ module RosettAi
10
+ module AiConfig
11
+ # Validates AI tool configuration files against the JSON Schema.
12
+ class Validator
13
+ SCHEMA_PATH = File.join(RosettAi.root, 'conf', 'schemas', 'ai_config_schema.json')
14
+
15
+ # Validates AI config data against the schema.
16
+ #
17
+ # @param data [Hash] parsed ai_config.yml data
18
+ # @return [Array<String>] list of validation errors (empty if valid)
19
+ def validate(data)
20
+ schema = load_schema
21
+ schemer = JSONSchemer.schema(schema)
22
+ errors = schemer.validate(data)
23
+ errors.map { |error| format_error(error) }
24
+ end
25
+
26
+ # @param data [Hash] AI config data
27
+ # @return [Boolean] true if data is valid
28
+ def valid?(data)
29
+ validate(data).empty?
30
+ end
31
+
32
+ private
33
+
34
+ def load_schema
35
+ JSON.parse(File.read(SCHEMA_PATH))
36
+ end
37
+
38
+ def format_error(error)
39
+ path = error['data_pointer']
40
+ message = error['type']
41
+ "#{path}: #{message}"
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,99 @@
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 Authorship
8
+ # Compiles authorship attribution sections for engine output.
9
+ #
10
+ # Generates markdown sections for CLAUDE.md, AGENTS.md, and other
11
+ # engine-specific output based on the active disclosure level.
12
+ # Ensures compiled metadata never exceeds what the disclosure level
13
+ # permits — per-file details are stripped at any level below full.
14
+ #
15
+ # @author hugo
16
+ # @author claude
17
+ class AttributionCompiler
18
+ FILES_KEYS = ['files', :files].freeze
19
+
20
+ # @param disclosure_policy [DisclosurePolicy] active disclosure policy
21
+ def initialize(disclosure_policy:)
22
+ @policy = disclosure_policy
23
+ end
24
+
25
+ # Compiles an attribution section for inclusion in engine output.
26
+ #
27
+ # @param engine_name [String] active engine display name
28
+ # @param provider [String] engine provider
29
+ # @param provenance_entries [Array<Hash>] recent provenance entries
30
+ # @return [Hash] with +:section+ (String, nil) and +:warnings+ (Array<String>)
31
+ def compile(engine_name:, provider:, provenance_entries: [])
32
+ warnings = []
33
+ return { section: nil, warnings: warnings } unless @policy.disclose?
34
+
35
+ safe_entries = sanitize_entries(provenance_entries)
36
+ section = build_section(engine_name, provider, safe_entries, warnings)
37
+ { section: section, warnings: warnings }
38
+ end
39
+
40
+ private
41
+
42
+ # Strips per-file data from entries when disclosure level does not
43
+ # permit it, preventing metadata leakage.
44
+ def sanitize_entries(entries)
45
+ return entries if @policy.include_per_file?
46
+
47
+ entries.map { |e| e.reject { |k| FILES_KEYS.include?(k) } }
48
+ end
49
+
50
+ def build_section(engine_name, provider, entries, warnings)
51
+ lines = []
52
+
53
+ if @policy.include_attribution?
54
+ lines << '## AI Attribution'
55
+ lines << ''
56
+ lines << "This project uses AI-assisted development via #{engine_name} (#{provider})."
57
+ lines << 'The human operator is always the accountable author.'
58
+ lines << 'AI tools are not listed as copyright holders.'
59
+ elsif @policy.include_trailer_guidance?
60
+ lines << "<!-- AI-assisted development via #{engine_name} (#{provider}) -->"
61
+ end
62
+
63
+ append_trailer_guidance(lines) if @policy.include_trailer_guidance?
64
+ append_per_file_summary(lines, entries, warnings) if @policy.include_per_file?
65
+
66
+ lines.join("\n")
67
+ end
68
+
69
+ def append_trailer_guidance(lines)
70
+ lines << ''
71
+ lines << '### Commit Trailers'
72
+ lines << ''
73
+ lines << 'Use standardised commit trailers to attribute AI involvement:'
74
+ lines << ''
75
+ lines << '- `AI-Generated-By`: code primarily generated by AI'
76
+ lines << '- `AI-Co-Author`: code co-authored with AI'
77
+ lines << '- `AI-Assisted-By`: AI used for suggestions or review'
78
+ lines << '- `AI-Reviewed-By`: AI reviewed human-written code'
79
+ end
80
+
81
+ def append_per_file_summary(lines, entries, _warnings)
82
+ return if entries.empty?
83
+
84
+ lines << ''
85
+ lines << '### Per-File AI Involvement'
86
+ lines << ''
87
+
88
+ entries.each do |entry|
89
+ role = entry['ai_role'] || entry[:ai_role]
90
+ files = entry['files'] || entry[:files] || []
91
+ files.each do |file|
92
+ path = file['path'] || file[:path]
93
+ lines << "- `#{path}`: #{role}"
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -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
+ module Authorship
8
+ # Manages disclosure levels for AI authorship attribution.
9
+ #
10
+ # Four levels control how much AI involvement metadata appears in
11
+ # compiled output and commit messages:
12
+ #
13
+ # - +none+: no authorship metadata emitted
14
+ # - +minimal+: brief note that AI tools were used
15
+ # - +standard+: AI Attribution section in compiled output
16
+ # - +full+: per-file AI involvement summary
17
+ #
18
+ # Disclosure level is read from +.rosett-ai/config.yml+ under
19
+ # +authorship.disclosure+, not from behaviour YAML.
20
+ #
21
+ # @author hugo
22
+ # @author claude
23
+ class DisclosurePolicy
24
+ LEVELS = ['none', 'minimal', 'standard', 'full'].freeze
25
+ DEFAULT_LEVEL = 'standard'
26
+
27
+ attr_reader :level
28
+
29
+ # @param level [String] one of {LEVELS}
30
+ # @raise [ArgumentError] if level is not valid
31
+ def initialize(level: DEFAULT_LEVEL)
32
+ validate_level!(level)
33
+ @level = level.freeze
34
+ end
35
+
36
+ # Loads the disclosure level from a config data hash.
37
+ #
38
+ # @param data [Hash] configuration hash (e.g. from YAML config file)
39
+ # @return [DisclosurePolicy] policy with configured level
40
+ def self.from_config(data)
41
+ level = data.dig('authorship', 'disclosure') || DEFAULT_LEVEL
42
+ new(level: level)
43
+ rescue ArgumentError
44
+ new(level: DEFAULT_LEVEL)
45
+ end
46
+
47
+ # @return [Boolean] true if any metadata should be emitted
48
+ def disclose?
49
+ @level != 'none'
50
+ end
51
+
52
+ # @return [Boolean] true if compiled output should include attribution section
53
+ def include_attribution?
54
+ at_least?('standard')
55
+ end
56
+
57
+ # @return [Boolean] true if per-file AI involvement should be included
58
+ def include_per_file?
59
+ at_least?('full')
60
+ end
61
+
62
+ # @return [Boolean] true if trailer guidance should be in compiled output
63
+ def include_trailer_guidance?
64
+ at_least?('minimal')
65
+ end
66
+
67
+ private
68
+
69
+ def at_least?(minimum)
70
+ LEVELS.index(@level) >= LEVELS.index(minimum)
71
+ end
72
+
73
+ def validate_level!(level)
74
+ return if LEVELS.include?(level)
75
+
76
+ raise ArgumentError,
77
+ "Invalid disclosure level '#{level}'. Allowed: #{LEVELS.join(', ')}"
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,39 @@
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 Authorship
8
+ # Validates that Human-Reviewed-By differs from the commit author.
9
+ #
10
+ # Self-review (where the submitter is also the reviewer) does not
11
+ # satisfy the review requirement. At advisory level this produces
12
+ # a warning; at strict level it produces an error.
13
+ class ReviewValidator
14
+ # Result of a review validation check.
15
+ #
16
+ # @return [Hash] with +:valid+ (Boolean) and +:message+ (String, nil)
17
+ def validate(author:, reviewer:)
18
+ return success if author.nil? || reviewer.nil?
19
+ return success unless normalize(author) == normalize(reviewer)
20
+
21
+ failure('Human-Reviewed-By must differ from the commit author (self-review detected)')
22
+ end
23
+
24
+ private
25
+
26
+ def normalize(identity)
27
+ identity.to_s.strip.downcase
28
+ end
29
+
30
+ def success
31
+ { valid: true, message: nil }
32
+ end
33
+
34
+ def failure(message)
35
+ { valid: false, message: message }
36
+ end
37
+ end
38
+ end
39
+ 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 Authorship
8
+ # Generates commit trailer templates for AI authorship.
9
+ #
10
+ # Trailer format follows the convention:
11
+ # Role: Tool Version (Provider) <email>
12
+ #
13
+ # Engine capability manifests declare whether trailer support is
14
+ # available; engines without trailer support get a warning.
15
+ #
16
+ # @author hugo
17
+ # @author claude
18
+ class TrailerGenerator
19
+ TRAILER_ROLES = RosettAi::Provenance::Entry::ALLOWED_ROLES
20
+
21
+ # Generates a trailer string for a given engine.
22
+ #
23
+ # @param engine_name [String] engine display name (e.g. "Claude Opus 4.6")
24
+ # @param provider [String] engine provider (e.g. "Anthropic")
25
+ # @param role [String] one of {TRAILER_ROLES}
26
+ # @return [String] formatted trailer
27
+ def generate(engine_name:, provider:, role:)
28
+ validate_role!(role)
29
+ "#{role}: #{engine_name} (#{provider})"
30
+ end
31
+
32
+ # Generates all four trailer templates for an engine.
33
+ #
34
+ # @param engine_name [String] engine display name
35
+ # @param provider [String] engine provider
36
+ # @return [Array<String>] all trailer templates
37
+ def generate_all(engine_name:, provider:)
38
+ TRAILER_ROLES.map do |role|
39
+ generate(engine_name: engine_name, provider: provider, role: role)
40
+ end
41
+ end
42
+
43
+ # Formats trailers as a block suitable for documentation.
44
+ #
45
+ # @param engine_name [String] engine display name
46
+ # @param provider [String] engine provider
47
+ # @return [String] newline-separated trailer block
48
+ def trailer_block(engine_name:, provider:)
49
+ generate_all(engine_name: engine_name, provider: provider).join("\n")
50
+ end
51
+
52
+ # Validates a tool name against known engine manifests.
53
+ #
54
+ # @param tool_name [String] tool name from a trailer (e.g. "Claude Opus 4.6")
55
+ # @param strict [Boolean] when true, unknown tools raise AuthorshipError
56
+ # @return [Array<String>] warnings (empty if tool is known)
57
+ def validate_tool(tool_name:, strict: false)
58
+ known = known_engine_names
59
+ return [] if known.empty? # no engines installed — skip validation
60
+ return [] if known.any? { |name| tool_name.include?(name) }
61
+
62
+ message = "Unknown AI tool '#{tool_name}' — not found in any engine manifest. " \
63
+ "Known engines: #{known.join(', ')}"
64
+ raise RosettAi::AuthorshipError, message if strict
65
+
66
+ [message]
67
+ end
68
+
69
+ private
70
+
71
+ def validate_role!(role)
72
+ return if TRAILER_ROLES.include?(role)
73
+
74
+ raise ArgumentError,
75
+ "Invalid trailer role '#{role}'. Allowed: #{TRAILER_ROLES.join(', ')}"
76
+ end
77
+
78
+ def known_engine_names
79
+ RosettAi::Engines::Registry.available.filter_map do |engine_name|
80
+ manifest = RosettAi::Engines::Registry.manifest(engine_name)
81
+ manifest['display_name']
82
+ rescue RosettAi::Error
83
+ nil
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end