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,71 @@
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 Policy
8
+ # Manages glob-based deny list patterns for AI tool access control.
9
+ #
10
+ # Deny list patterns prevent AI tools from reading or modifying
11
+ # sensitive files such as SSH keys, credentials, and secrets.
12
+ # Patterns are compiled to engine-specific formats during compilation.
13
+ #
14
+ # All patterns are sanitized (ANSI/control chars stripped) and
15
+ # validated (path traversal rejected) on initialization.
16
+ #
17
+ # @author hugo
18
+ # @author claude
19
+ class DenyList
20
+ PATH_TRAVERSAL_PATTERN = %r{(?:^|/)\.\./} # matches ../ at start or after /
21
+
22
+ attr_reader :patterns
23
+
24
+ # @param patterns [Array<String>] glob patterns to deny
25
+ # @raise [RosettAi::PolicyError] if any pattern contains path traversal
26
+ def initialize(patterns: [])
27
+ sanitized = patterns.map { |pat| RosettAi::TextSanitizer.strip_ansi(pat) }
28
+ validate_patterns!(sanitized)
29
+ @patterns = sanitized.map(&:freeze).freeze
30
+ end
31
+
32
+ # Tests whether a file path matches any deny pattern.
33
+ #
34
+ # @param path [String] relative file path to test
35
+ # @return [Boolean] true if the path matches any deny pattern
36
+ def denied?(path)
37
+ @patterns.any? { |pattern| File.fnmatch?(pattern, path, File::FNM_PATHNAME | File::FNM_DOTMATCH) }
38
+ end
39
+
40
+ # Merges another deny list into this one (union of patterns).
41
+ #
42
+ # @param other [DenyList] deny list to merge
43
+ # @return [DenyList] new deny list with combined patterns
44
+ def merge(other)
45
+ self.class.new(patterns: (@patterns + other.patterns).uniq)
46
+ end
47
+
48
+ # @return [Integer] number of patterns
49
+ def size
50
+ @patterns.size
51
+ end
52
+
53
+ # @return [Boolean] true if no patterns defined
54
+ def empty?
55
+ @patterns.empty?
56
+ end
57
+
58
+ private
59
+
60
+ def validate_patterns!(patterns)
61
+ patterns.each do |pattern|
62
+ next unless PATH_TRAVERSAL_PATTERN.match?(pattern)
63
+
64
+ raise RosettAi::PolicyError,
65
+ "Deny-list pattern contains path traversal: '#{pattern}'. " \
66
+ 'Patterns must not reference parent directories with ..'
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,37 @@
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 Policy
8
+ # Scans files for AI-MODIFICATION opt-out markers.
9
+ #
10
+ # Files containing +# AI-MODIFICATION: disabled+ are added to the
11
+ # protected files list during compilation.
12
+ class OptOutScanner
13
+ MARKER = 'AI-MODIFICATION: disabled'
14
+
15
+ # Scans a single file for opt-out markers.
16
+ #
17
+ # @param path [String] absolute or relative file path
18
+ # @return [Boolean] true if the file contains an opt-out marker
19
+ def opted_out?(path)
20
+ return false unless File.exist?(path)
21
+ return false unless File.readable?(path)
22
+
23
+ File.foreach(path).any? { |line| line.include?(MARKER) }
24
+ rescue StandardError
25
+ false
26
+ end
27
+
28
+ # Scans multiple files and returns those with opt-out markers.
29
+ #
30
+ # @param paths [Array<String>] file paths to scan
31
+ # @return [Array<String>] paths that contain opt-out markers
32
+ def scan(paths)
33
+ paths.select { |path| opted_out?(path) }
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,84 @@
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 Policy
8
+ # Compiles policy definitions to engine-specific output.
9
+ #
10
+ # Translates deny lists, protected files, and rate limits into
11
+ # engine-native configuration formats. When a parent policy is
12
+ # provided, enforces tighten-only inheritance at compile time.
13
+ #
14
+ # @author hugo
15
+ # @author claude
16
+ class PolicyCompiler
17
+ # @param deny_list [DenyList] deny list to compile
18
+ # @param protected_files [ProtectedFiles] protected files to compile
19
+ # @param parent_deny_list [DenyList, nil] parent policy deny list for scope enforcement
20
+ def initialize(deny_list:, protected_files:, parent_deny_list: nil)
21
+ @deny_list = deny_list
22
+ @protected_files = protected_files
23
+ @parent_deny_list = parent_deny_list
24
+ end
25
+
26
+ # Compiles policies for a specific engine.
27
+ #
28
+ # @param engine [String] target engine name
29
+ # @return [Hash] with +:settings+ (Hash), +:annotations+ (String), +:warnings+ (Array)
30
+ def compile(engine:)
31
+ warnings = []
32
+ enforce_scope!(warnings)
33
+ settings = compile_deny_list(engine, warnings)
34
+ annotations = compile_protected_files(engine)
35
+
36
+ { settings: settings, annotations: annotations, warnings: warnings }
37
+ end
38
+
39
+ private
40
+
41
+ def enforce_scope!(warnings)
42
+ return unless @parent_deny_list
43
+
44
+ hierarchy = RosettAi::Policy::TierHierarchy.new
45
+ result = hierarchy.validate_tighten_only(parent: @parent_deny_list, child: @deny_list)
46
+ return if result[:valid]
47
+
48
+ result[:violations].each { |v| warnings << v }
49
+ end
50
+
51
+ def compile_deny_list(engine, warnings)
52
+ return {} if @deny_list.empty?
53
+
54
+ case engine
55
+ when 'claude'
56
+ { 'deniedPaths' => @deny_list.patterns }
57
+ when 'cursor'
58
+ { 'ignore_patterns' => @deny_list.patterns }
59
+ when 'agents_md'
60
+ { 'denied_access' => @deny_list.patterns }
61
+ else
62
+ warnings << "Engine '#{engine}' may not support deny list compilation"
63
+ { 'deny_patterns' => @deny_list.patterns }
64
+ end
65
+ end
66
+
67
+ def compile_protected_files(engine)
68
+ return '' if @protected_files.empty?
69
+
70
+ case engine
71
+ when 'agents_md'
72
+ lines = @protected_files.paths.map { |path| "- `#{path}`: READ-ONLY (AI must not modify)" }
73
+ "## Protected Files\n\n#{lines.join("\n")}"
74
+ when 'claude', 'cursor'
75
+ lines = @protected_files.paths.map { |path| "# Protected (read-only): #{path}" }
76
+ lines.join("\n")
77
+ else
78
+ lines = @protected_files.paths.map { |path| "- #{path} (read-only)" }
79
+ lines.join("\n")
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,47 @@
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 Policy
8
+ # Manages the list of files that AI tools may read but not modify.
9
+ #
10
+ # Protected files are compiled to engine-specific read-only annotations.
11
+ # Human override requires an explicit waiver in the policy configuration.
12
+ class ProtectedFiles
13
+ attr_reader :paths
14
+
15
+ # @param paths [Array<String>] relative file paths
16
+ def initialize(paths: [])
17
+ @paths = paths.map(&:freeze).freeze
18
+ end
19
+
20
+ # Tests whether a file path is protected (read-only for AI).
21
+ #
22
+ # @param path [String] relative file path
23
+ # @return [Boolean] true if the file is protected
24
+ def protected?(path)
25
+ @paths.include?(path)
26
+ end
27
+
28
+ # Merges another protected files list (union of paths).
29
+ #
30
+ # @param other [ProtectedFiles] protected files to merge
31
+ # @return [ProtectedFiles] new list with combined paths
32
+ def merge(other)
33
+ self.class.new(paths: (@paths + other.paths).uniq)
34
+ end
35
+
36
+ # @return [Integer] number of protected files
37
+ def size
38
+ @paths.size
39
+ end
40
+
41
+ # @return [Boolean] true if no files protected
42
+ def empty?
43
+ @paths.empty?
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,48 @@
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 Policy
8
+ # Enforces tighten-only policy inheritance across organisational tiers.
9
+ #
10
+ # Policy hierarchy: org > team > project. Child tiers can add patterns
11
+ # or restrictions but can never remove patterns set by parent tiers.
12
+ class TierHierarchy
13
+ TIERS = ['mandatory', 'advisory', 'informational'].freeze
14
+ SCOPES = ['org', 'team', 'project'].freeze
15
+
16
+ # Validates that a child policy does not loosen a parent policy.
17
+ #
18
+ # @param parent [DenyList] parent deny list
19
+ # @param child [DenyList] child deny list
20
+ # @return [Hash] with +:valid+ (Boolean) and +:violations+ (Array<String>)
21
+ def validate_tighten_only(parent:, child:)
22
+ removed = parent.patterns - child.patterns
23
+ return { valid: true, violations: [] } if removed.empty?
24
+
25
+ violations = removed.map do |pattern|
26
+ "Cannot remove deny-list pattern from parent policy: #{pattern}"
27
+ end
28
+ { valid: false, violations: violations }
29
+ end
30
+
31
+ # Validates that a policy tier is valid.
32
+ #
33
+ # @param tier [String] policy tier name
34
+ # @return [Boolean] true if valid
35
+ def valid_tier?(tier)
36
+ TIERS.include?(tier)
37
+ end
38
+
39
+ # Validates that a scope is valid.
40
+ #
41
+ # @param scope [String] scope name
42
+ # @return [Boolean] true if valid
43
+ def valid_scope?(scope)
44
+ SCOPES.include?(scope)
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,35 @@
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 Policy
10
+ # Validates policy files against the policy JSON Schema.
11
+ #
12
+ # Sanitizes all string values (ANSI/control char stripping) before
13
+ # schema validation to prevent injection via policy definitions.
14
+ #
15
+ # @author hugo
16
+ # @author claude
17
+ class Validator
18
+ SCHEMA_PATH = File.expand_path('../../../conf/schemas/policy_schema.json', __dir__)
19
+
20
+ # @return [Array<String>] validation errors (empty if valid)
21
+ def validate(data)
22
+ sanitized = RosettAi::TextSanitizer.sanitize_for_display(data)
23
+ schema = JSONSchemer.schema(Pathname.new(SCHEMA_PATH))
24
+ schema.validate(sanitized).map do |error|
25
+ "#{error['data_pointer']}: #{error['type']}"
26
+ end
27
+ end
28
+
29
+ # @return [Boolean] true if data passes schema validation
30
+ def valid?(data)
31
+ validate(data).empty?
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-License-Identifier: GPL-3.0-only
4
+ # Copyright (C) 2026 Hugo Antonio Sepulveda Manriquez / NeatNerds
5
+
6
+ module RosettAi
7
+ # Lightweight profiler for CLI command performance measurement.
8
+ #
9
+ # Enabled via `RAI_PROFILE=1` environment variable.
10
+ # Output goes to stderr to preserve piped command compatibility.
11
+ # Uses monotonic clock for accurate elapsed time measurement.
12
+ #
13
+ # @example
14
+ # profiler = RosettAi::Profiler.new('compile')
15
+ # profiler.measure('load') { load_files }
16
+ # profiler.measure('validate') { validate_files }
17
+ # profiler.report
18
+ #
19
+ # @author hugo
20
+ # @author claude
21
+ class Profiler
22
+ # @return [Boolean] true if profiling is enabled
23
+ def self.enabled?
24
+ ENV['RAI_PROFILE'] == '1'
25
+ end
26
+
27
+ # @return [Profiler] real profiler if enabled, no-op otherwise
28
+ def self.for(command_name)
29
+ enabled? ? new(command_name) : NullProfiler.new
30
+ end
31
+
32
+ attr_reader :command_name, :phases
33
+
34
+ # @param command_name [String]
35
+ def initialize(command_name)
36
+ @command_name = command_name
37
+ @phases = []
38
+ @start_time = monotonic_now
39
+ end
40
+
41
+ # Measure a named phase.
42
+ #
43
+ # @param phase_name [String]
44
+ # @return [Object] block return value
45
+ def measure(phase_name)
46
+ start = monotonic_now
47
+ result = yield
48
+ elapsed = monotonic_now - start
49
+ @phases << { name: phase_name, elapsed_ms: (elapsed * 1000).round(1) }
50
+ result
51
+ end
52
+
53
+ # Print profiling summary to stderr.
54
+ #
55
+ # @return [void]
56
+ def report
57
+ total = (monotonic_now - @start_time) * 1000
58
+ parts = @phases.map { |phase| "#{phase[:name]}: #{phase[:elapsed_ms]}ms" }
59
+ parts << "total: #{total.round(1)}ms"
60
+ warn "[profile] #{parts.join(' | ')}"
61
+ end
62
+
63
+ private
64
+
65
+ def monotonic_now
66
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
67
+ end
68
+
69
+ # No-op profiler when RAI_PROFILE is not set.
70
+ # Zero overhead — all methods are empty.
71
+ class NullProfiler
72
+ def measure(_phase_name)
73
+ yield
74
+ end
75
+
76
+ def report; end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,126 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-License-Identifier: GPL-3.0-only
4
+ # Copyright (C) 2026 Hugo Antonio Sepulveda Manriquez / NeatNerds
5
+
6
+ require 'yaml'
7
+ require 'digest'
8
+
9
+ module RosettAi
10
+ module Project
11
+ # Detects configuration drift in rosett-ai-managed projects.
12
+ #
13
+ # Supports two drift types:
14
+ # - Forward drift: source YAML differs from compiled native config
15
+ # - Template drift: project config differs from template baseline
16
+ #
17
+ # @author hugo
18
+ # @author claude
19
+ class DriftDetector
20
+ DRIFT_TYPES = ['forward', 'template'].freeze
21
+
22
+ # @param project_root [Pathname] root directory of the project
23
+ def initialize(project_root:)
24
+ @project_root = project_root
25
+ @project_dir = project_root.join('.rosett-ai')
26
+ end
27
+
28
+ # Detects drift of the specified type.
29
+ #
30
+ # @param type [String, nil] 'forward', 'template', or nil (both)
31
+ # @return [Hash] drift results with :forward and/or :template keys
32
+ def detect(type: nil)
33
+ results = {}
34
+
35
+ results[:forward] = detect_forward_drift if type.nil? || type == 'forward'
36
+
37
+ results[:template] = detect_template_drift if type.nil? || type == 'template'
38
+
39
+ results
40
+ end
41
+
42
+ # @return [Boolean] true if any drift detected
43
+ def drifted?(type: nil)
44
+ results = detect(type: type)
45
+ results.values.any?(&:any?)
46
+ end
47
+
48
+ private
49
+
50
+ def detect_forward_drift
51
+ drifts = []
52
+ source_dir = @project_dir.join('conf', 'behaviour')
53
+ return drifts unless source_dir.directory?
54
+
55
+ Dir.glob(source_dir.join('*.yml').to_s).each do |src_path|
56
+ src = Pathname.new(src_path)
57
+ compiled_name = File.basename(src_path, '.yml')
58
+ compiled = find_compiled_file(compiled_name)
59
+ next unless compiled&.exist?
60
+
61
+ src_hash = Digest::SHA256.file(src.to_s).hexdigest
62
+ compiled_hash = Digest::SHA256.file(compiled.to_s).hexdigest
63
+
64
+ next if src_hash == compiled_hash
65
+
66
+ drifts << {
67
+ file: src.relative_path_from(@project_root).to_s,
68
+ compiled: compiled.relative_path_from(@project_root).to_s,
69
+ type: 'forward'
70
+ }
71
+ end
72
+
73
+ drifts
74
+ end
75
+
76
+ def detect_template_drift
77
+ drifts = []
78
+ origin_file = @project_dir.join('.template_origin.yml')
79
+ return drifts unless origin_file.exist?
80
+
81
+ template_dir = resolve_template_dir(origin_file)
82
+ return drifts unless template_dir&.directory?
83
+
84
+ collect_template_drifts(template_dir, drifts)
85
+ end
86
+
87
+ def resolve_template_dir(origin_file)
88
+ origin = YAML.safe_load(origin_file.read)
89
+ RosettAi.root.join(TemplateApplier::TEMPLATES_DIR, origin['template'])
90
+ end
91
+
92
+ def collect_template_drifts(template_dir, drifts)
93
+ Dir.glob(template_dir.join('**', '*').to_s).each do |tmpl_path|
94
+ tmpl = Pathname.new(tmpl_path)
95
+ next if tmpl.directory?
96
+
97
+ check_template_file_drift(tmpl, template_dir, drifts)
98
+ end
99
+
100
+ drifts
101
+ end
102
+
103
+ def check_template_file_drift(tmpl, template_dir, drifts)
104
+ relative = tmpl.relative_path_from(template_dir)
105
+ project_file = @project_dir.join(relative)
106
+ return unless project_file.exist?
107
+ return if Digest::SHA256.file(tmpl.to_s).hexdigest == Digest::SHA256.file(project_file.to_s).hexdigest
108
+
109
+ drifts << {
110
+ file: project_file.relative_path_from(@project_root).to_s,
111
+ template: relative.to_s,
112
+ type: 'template'
113
+ }
114
+ end
115
+
116
+ def find_compiled_file(name)
117
+ # Check common compiled output locations
118
+ candidates = [
119
+ @project_root.join('.claude', 'rules', "#{name}.md"),
120
+ @project_root.join('rules', "#{name}.md")
121
+ ]
122
+ candidates.find(&:exist?)
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-License-Identifier: GPL-3.0-only
4
+ # Copyright (C) 2026 Hugo Antonio Sepulveda Manriquez / NeatNerds
5
+
6
+ require 'yaml'
7
+ require_relative 'template_applier'
8
+ require_relative 'drift_detector'
9
+ require_relative 'sync_manager'
10
+
11
+ module RosettAi
12
+ module Project
13
+ # High-level project lifecycle manager.
14
+ #
15
+ # Orchestrates template application, drift detection, upstream sync,
16
+ # and project health reporting. Delegates to specialised classes.
17
+ #
18
+ # @author hugo
19
+ # @author claude
20
+ class Manager
21
+ # @param project_root [Pathname] root directory of the project
22
+ def initialize(project_root:)
23
+ @project_root = project_root
24
+ @project_dir = project_root.join('.rosett-ai')
25
+ end
26
+
27
+ # Returns project metadata and health status.
28
+ #
29
+ # @return [Hash] project info including name, engine, template, behaviours
30
+ def info
31
+ validate_project!
32
+
33
+ config = load_project_config
34
+ origin = load_template_origin
35
+
36
+ {
37
+ project_name: config['project_name'] || File.basename(@project_root),
38
+ default_engine: config['default_engine'] || 'generic',
39
+ template: origin&.fetch('template', nil),
40
+ template_applied_at: origin&.fetch('applied_at', nil),
41
+ behaviours: count_behaviours,
42
+ designs: count_designs,
43
+ project_root: @project_root.to_s,
44
+ project_dir: @project_dir.to_s
45
+ }
46
+ end
47
+
48
+ # Returns comprehensive project status.
49
+ #
50
+ # @return [Hash] status with :info, :drift, :health
51
+ def status
52
+ project_info = info
53
+ drift = DriftDetector.new(project_root: @project_root)
54
+ drift_results = drift.detect
55
+
56
+ {
57
+ info: project_info,
58
+ drift: drift_results,
59
+ health: compute_health(project_info, drift_results)
60
+ }
61
+ end
62
+
63
+ private
64
+
65
+ def validate_project!
66
+ return if @project_dir.directory?
67
+
68
+ raise RosettAi::ProjectError,
69
+ 'Not inside an rai project. Run rai init --project first.'
70
+ end
71
+
72
+ def load_project_config
73
+ config_path = @project_dir.join('config.yml')
74
+ return {} unless config_path.exist?
75
+
76
+ YAML.safe_load(config_path.read) || {}
77
+ end
78
+
79
+ def load_template_origin
80
+ origin_path = @project_dir.join('.template_origin.yml')
81
+ return nil unless origin_path.exist?
82
+
83
+ YAML.safe_load(origin_path.read)
84
+ end
85
+
86
+ def count_behaviours
87
+ dir = @project_dir.join('conf', 'behaviour')
88
+ return 0 unless dir.directory?
89
+
90
+ Dir.glob(dir.join('*.yml').to_s).size
91
+ end
92
+
93
+ def count_designs
94
+ dir = @project_dir.join('conf', 'design')
95
+ return 0 unless dir.directory?
96
+
97
+ Dir.glob(dir.join('*.yml').to_s).size
98
+ end
99
+
100
+ def compute_health(project_info, drift_results)
101
+ issues = []
102
+ issues << 'No template applied' unless project_info[:template]
103
+ issues << 'No behaviours defined' if project_info[:behaviours].zero?
104
+
105
+ forward_drift = drift_results.fetch(:forward, [])
106
+ template_drift = drift_results.fetch(:template, [])
107
+
108
+ issues << "Forward drift detected (#{forward_drift.size} files)" unless forward_drift.empty?
109
+ issues << "Template drift detected (#{template_drift.size} files)" unless template_drift.empty?
110
+
111
+ { healthy: issues.empty?, issues: issues }
112
+ end
113
+ end
114
+ end
115
+ end