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,129 @@
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 'pathname'
7
+
8
+ module RosettAi
9
+ # Checks that all Ruby version references across the codebase are consistent
10
+ # with the canonical version declared in .ruby-version.
11
+ #
12
+ # Scans non-binary project files for version strings matching the MAJOR.MINOR
13
+ # series (e.g. 3.3.x) and reports any that don't match the expected PATCH level.
14
+ class VersionConsistencyChecker
15
+ EXCLUDED_DIRS = ['vendor', 'tmp', 'coverage', '.git', '.bundle'].freeze
16
+ EXCLUDED_FILES = ['.ruby-version', 'CHANGELOG.md', 'Gemfile.lock',
17
+ 'Gemfile.integration.lock',
18
+ 'spec/rosett_ai/version_consistency_checker_spec.rb'].freeze
19
+ EXCLUDED_PREFIXES = ['doc/changes/', 'doc/claude-sessions/', 'doc/INCIDENT_REPORT',
20
+ 'doc/PACKAGING', 'doc/issues/', 'doc/context/', 'conf/design/'].freeze
21
+ EXCLUDED_EXTENSIONS = ['.aux', '.toc', '.out', '.ptc', '.log', '.idx', '.ind',
22
+ '.ilg', '.bbl', '.blg', '.bcf', '.fls', '.fdb_latexmk',
23
+ '.run.xml', '.synctex.gz'].freeze
24
+
25
+ attr_reader :results
26
+
27
+ def initialize(project_dir: Dir.pwd)
28
+ @project_dir = Pathname.new(project_dir)
29
+ @results = { expected_version: nil, references: [], mismatches: [], consistent: true }
30
+ end
31
+
32
+ def check
33
+ version = read_ruby_version
34
+ @results = { expected_version: version, references: [], mismatches: [], consistent: true }
35
+
36
+ pattern = build_version_pattern(version)
37
+ scan_files(version, pattern)
38
+
39
+ @results[:consistent] = @results[:mismatches].empty?
40
+ @results
41
+ end
42
+
43
+ def consistent?
44
+ check if @results[:expected_version].nil?
45
+ @results[:consistent]
46
+ end
47
+
48
+ private
49
+
50
+ def read_ruby_version
51
+ ruby_version_file = @project_dir.join('.ruby-version')
52
+ raise RosettAi::ToolingError, "Missing .ruby-version in #{@project_dir}" unless ruby_version_file.exist?
53
+
54
+ ruby_version_file.read.strip
55
+ end
56
+
57
+ def build_version_pattern(version)
58
+ major, minor = version.split('.')[0..1]
59
+ /\b#{Regexp.escape(major)}\.#{Regexp.escape(minor)}\.\d+\b/
60
+ end
61
+
62
+ def scan_files(expected, pattern)
63
+ project_files.each do |file|
64
+ scan_file(file, expected, pattern)
65
+ end
66
+ end
67
+
68
+ def project_files
69
+ Dir.glob(@project_dir.join('**', '*'), File::FNM_DOTMATCH)
70
+ .select { |f| File.file?(f) }
71
+ .reject { |f| excluded?(f) }
72
+ .reject { |f| binary?(f) }
73
+ .sort
74
+ end
75
+
76
+ def excluded?(file)
77
+ relative = Pathname.new(file).relative_path_from(@project_dir).to_s
78
+
79
+ return true if EXCLUDED_DIRS.any? { |dir| relative.start_with?("#{dir}/") }
80
+ return true if EXCLUDED_FILES.include?(relative)
81
+ return true if EXCLUDED_PREFIXES.any? { |prefix| relative.start_with?(prefix) }
82
+ return true if EXCLUDED_EXTENSIONS.any? { |ext| relative.end_with?(ext) }
83
+
84
+ false
85
+ end
86
+
87
+ def binary?(file)
88
+ sample = File.read(file, 512)
89
+ return true if sample.nil?
90
+
91
+ sample.include?("\x00")
92
+ rescue ArgumentError, Errno::ENOENT
93
+ # ArgumentError: invalid encoding
94
+ # ENOENT: file deleted between glob and read (e.g., temp files)
95
+ true
96
+ end
97
+
98
+ def scan_file(file, expected, pattern)
99
+ relative = Pathname.new(file).relative_path_from(@project_dir).to_s
100
+
101
+ File.readlines(file, encoding: 'UTF-8').each_with_index do |line, index|
102
+ line.scan(pattern).each do |found|
103
+ next if constraint_line?(line)
104
+ next if suppressed_line?(line)
105
+
106
+ reference = {
107
+ file: relative,
108
+ line: index + 1,
109
+ found: found,
110
+ expected: expected
111
+ }
112
+
113
+ @results[:references] << reference
114
+ @results[:mismatches] << reference unless found == expected
115
+ end
116
+ end
117
+ rescue ArgumentError, Encoding::InvalidByteSequenceError
118
+ # Skip files with encoding issues
119
+ end
120
+
121
+ def constraint_line?(line)
122
+ line.match?(/[><=!~]{1,2}\s*\d+\.\d+/)
123
+ end
124
+
125
+ def suppressed_line?(line)
126
+ line.include?('rai:no-version-check')
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-License-Identifier: GPL-3.0-only
4
+ # Copyright (C) 2026 Hugo Antonio Sepulveda Manriquez / NeatNerds
5
+
6
+ require 'json'
7
+
8
+ module RosettAi
9
+ module Workflow
10
+ # Structured audit log for workflow execution.
11
+ #
12
+ # Records each step execution with timestamp, status, and duration.
13
+ # Supports resume by identifying completed steps from the log.
14
+ #
15
+ # @author hugo
16
+ # @author claude
17
+ class AuditLog
18
+ attr_reader :entries
19
+
20
+ # @param log_path [Pathname, nil] path to audit log file
21
+ def initialize(log_path: nil)
22
+ @log_path = log_path
23
+ @entries = load_entries
24
+ end
25
+
26
+ # Records a step execution result.
27
+ #
28
+ # @param workflow_name [String]
29
+ # @param step_name [String]
30
+ # @param status [String] 'pass', 'fail', or 'skip'
31
+ # @param duration_ms [Float]
32
+ # @param details [String, nil]
33
+ def record(workflow_name:, step_name:, status:, duration_ms: 0.0, details: nil)
34
+ entry = {
35
+ 'workflow' => workflow_name,
36
+ 'step' => step_name,
37
+ 'status' => status,
38
+ 'timestamp' => Time.now.utc.iso8601,
39
+ 'duration_ms' => duration_ms.round(1)
40
+ }
41
+ entry['details'] = details if details
42
+ @entries << entry
43
+ persist_entry(entry)
44
+ end
45
+
46
+ # Returns step names that completed successfully for a workflow.
47
+ #
48
+ # @param workflow_name [String]
49
+ # @return [Array<String>] completed step names
50
+ def completed_steps(workflow_name)
51
+ @entries
52
+ .select { |e| e['workflow'] == workflow_name && e['status'] == 'pass' }
53
+ .map { |e| e['step'] }
54
+ end
55
+
56
+ # @return [Hash] serializable summary
57
+ def to_h
58
+ {
59
+ 'entries' => @entries,
60
+ 'total' => @entries.size,
61
+ 'pass' => @entries.count { |e| e['status'] == 'pass' },
62
+ 'fail' => @entries.count { |e| e['status'] == 'fail' }
63
+ }
64
+ end
65
+
66
+ private
67
+
68
+ def load_entries
69
+ return [] unless @log_path&.exist?
70
+
71
+ @log_path.each_line.filter_map do |line|
72
+ JSON.parse(line.strip)
73
+ rescue JSON::ParserError
74
+ nil
75
+ end
76
+ end
77
+
78
+ def persist_entry(entry)
79
+ return unless @log_path
80
+
81
+ @log_path.dirname.mkpath
82
+ File.open(@log_path, 'a') { |f| f.puts(JSON.generate(entry)) }
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,142 @@
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 'schema_validator'
8
+ require_relative 'step_runner'
9
+ require_relative 'audit_log'
10
+
11
+ module RosettAi
12
+ module Workflow
13
+ # Orchestrates declarative workflow execution.
14
+ #
15
+ # Loads workflow YAML, validates against schema, and executes
16
+ # steps sequentially. Supports dry-run (simulate), resume from
17
+ # audit log, and per-step on_failure handling.
18
+ #
19
+ # @author hugo
20
+ # @author claude
21
+ class Engine
22
+ attr_reader :workflow_data, :audit_log
23
+
24
+ # @param workflow_path [Pathname] path to workflow YAML file
25
+ # @param audit_log_path [Pathname, nil] path to audit log file
26
+ def initialize(workflow_path:, audit_log_path: nil)
27
+ @workflow_path = workflow_path
28
+ @workflow_data = load_workflow
29
+ @audit_log = AuditLog.new(log_path: audit_log_path)
30
+ @step_runner = StepRunner.new
31
+ @validator = SchemaValidator.new
32
+ end
33
+
34
+ # Validates the workflow definition.
35
+ #
36
+ # @return [Array<String>] validation errors (empty if valid)
37
+ def validate
38
+ @validator.validate(@workflow_data)
39
+ end
40
+
41
+ # Returns step descriptions for dry-run mode.
42
+ #
43
+ # @return [Array<String>] descriptions of each step
44
+ def simulate
45
+ errors = validate
46
+ raise RosettAi::WorkflowError, "Workflow validation failed: #{errors.join(', ')}" unless errors.empty?
47
+
48
+ @workflow_data.fetch('steps').map do |step_def|
49
+ @step_runner.describe(step_def)
50
+ end
51
+ end
52
+
53
+ # Executes the workflow.
54
+ #
55
+ # Runs each step in order. Skips steps already completed (via audit log).
56
+ # Stops on first failure unless the step has `on_failure: continue`.
57
+ #
58
+ # @param resume [Boolean] skip already-completed steps
59
+ # @return [Hash] execution summary with :status, :results, :workflow
60
+ def execute(resume: false)
61
+ errors = validate
62
+ raise RosettAi::WorkflowError, "Workflow validation failed: #{errors.join(', ')}" unless errors.empty?
63
+
64
+ workflow_name = @workflow_data.fetch('name')
65
+ completed = resume ? @audit_log.completed_steps(workflow_name) : []
66
+ results = []
67
+ failed = false
68
+
69
+ @workflow_data.fetch('steps').each do |step_def|
70
+ step_name = step_def.fetch('name')
71
+
72
+ if completed.include?(step_name)
73
+ results << { step: step_name, status: 'skip', message: 'Already completed (resume)' }
74
+ next
75
+ end
76
+
77
+ if failed
78
+ results << skip_result(step_name)
79
+ record_audit(workflow_name, step_name, 'skip')
80
+ next
81
+ end
82
+
83
+ result = run_step(workflow_name, step_def)
84
+ results << result
85
+
86
+ failed = true if result[:status] == 'fail' && step_def.fetch('on_failure', 'stop') == 'stop'
87
+ end
88
+
89
+ build_summary(workflow_name, results)
90
+ end
91
+
92
+ private
93
+
94
+ def load_workflow
95
+ raise RosettAi::WorkflowError, "Workflow file not found: #{@workflow_path}" unless @workflow_path.exist?
96
+
97
+ data = YAML.safe_load(@workflow_path.read, permitted_classes: [Date])
98
+ raise RosettAi::WorkflowError, 'Workflow file is empty or invalid YAML' unless data.is_a?(Hash)
99
+
100
+ data
101
+ end
102
+
103
+ def run_step(workflow_name, step_def)
104
+ step_name = step_def.fetch('name')
105
+ result = @step_runner.run(step_def)
106
+ record_audit(workflow_name, step_name, result[:status], result[:duration_ms])
107
+ { step: step_name, **result }
108
+ end
109
+
110
+ def record_audit(workflow_name, step_name, status, duration_ms = 0.0)
111
+ @audit_log.record(
112
+ workflow_name: workflow_name,
113
+ step_name: step_name,
114
+ status: status,
115
+ duration_ms: duration_ms
116
+ )
117
+ end
118
+
119
+ def skip_result(step_name)
120
+ { step: step_name, status: 'skip', message: 'Skipped (previous step failed)' }
121
+ end
122
+
123
+ def build_summary(workflow_name, results)
124
+ overall = if results.any? { |r| r[:status] == 'fail' }
125
+ 'fail'
126
+ else
127
+ 'pass'
128
+ end
129
+
130
+ {
131
+ workflow: workflow_name,
132
+ status: overall,
133
+ total: results.size,
134
+ pass: results.count { |r| r[:status] == 'pass' },
135
+ fail: results.count { |r| r[:status] == 'fail' },
136
+ skip: results.count { |r| r[:status] == 'skip' },
137
+ results: results
138
+ }
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,82 @@
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 Workflow
8
+ # MCP-facing facade for workflow operations.
9
+ #
10
+ # Delegates to {Engine} for the actual workflow logic, providing
11
+ # the simplified interface expected by {Mcp::Tools::WorkflowTool}.
12
+ #
13
+ # @author hugo
14
+ # @author claude
15
+ class Manager
16
+ WORKFLOW_DIR = File.join(Dir.home, '.claude', 'conf', 'workflows')
17
+
18
+ def initialize
19
+ @workflow_dir = Pathname.new(WORKFLOW_DIR)
20
+ end
21
+
22
+ # Lists all available workflows.
23
+ #
24
+ # @return [Array<Hash>] workflow summaries
25
+ def list
26
+ workflow_files.map { |path| build_workflow_summary(path) }
27
+ end
28
+
29
+ # Validates a specific workflow by name.
30
+ #
31
+ # @param name [String] workflow name
32
+ # @return [Hash] with :errors key
33
+ def validate(name)
34
+ engine = build_engine(name)
35
+ { errors: engine.validate }
36
+ end
37
+
38
+ # Validates all workflows.
39
+ #
40
+ # @return [Hash] with :errors key
41
+ def validate_all
42
+ errors = workflow_files.flat_map { |path| validate_workflow_file(path) }
43
+ { errors: errors }
44
+ end
45
+
46
+ # Simulates a workflow without executing.
47
+ #
48
+ # @param name [String] workflow name
49
+ # @return [Hash] with :steps and :duration keys
50
+ def simulate(name)
51
+ engine = build_engine(name)
52
+ steps = engine.simulate
53
+ { steps: steps, duration: steps.size }
54
+ end
55
+
56
+ private
57
+
58
+ def workflow_files
59
+ return [] unless @workflow_dir.exist?
60
+
61
+ @workflow_dir.glob('*.yml').sort
62
+ end
63
+
64
+ def build_workflow_summary(path)
65
+ data = YAML.safe_load_file(path, permitted_classes: [Date])
66
+ { name: data['name'], description: data['description'], path: path.to_s }
67
+ end
68
+
69
+ def validate_workflow_file(path)
70
+ engine = Engine.new(workflow_path: path)
71
+ engine.validate.map { |e| "#{File.basename(path)}: #{e}" }
72
+ end
73
+
74
+ def build_engine(name)
75
+ path = @workflow_dir.join("#{name}.yml")
76
+ raise RosettAi::WorkflowError, "Workflow '#{name}' not found" unless path.exist?
77
+
78
+ Engine.new(workflow_path: path)
79
+ end
80
+ end
81
+ end
82
+ end
@@ -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
+ require 'json_schemer'
7
+
8
+ module RosettAi
9
+ module Workflow
10
+ # Validates workflow YAML files against the workflow JSON Schema.
11
+ #
12
+ # Additionally enforces security constraints: shell steps must use
13
+ # array-form commands, prompt steps must not contain secret literals.
14
+ #
15
+ # @author hugo
16
+ # @author claude
17
+ class SchemaValidator
18
+ MAX_STEPS = 50
19
+ SCHEMA_PATH = File.expand_path('../../../conf/schemas/workflow_schema.json', __dir__)
20
+
21
+ # @param data [Hash] parsed workflow YAML
22
+ # @return [Array<String>] validation errors (empty if valid)
23
+ def validate(data)
24
+ errors = schema_errors(data)
25
+ errors += security_errors(data) if errors.empty?
26
+ errors
27
+ end
28
+
29
+ # @param data [Hash] parsed workflow YAML
30
+ # @return [Boolean]
31
+ def valid?(data)
32
+ validate(data).empty?
33
+ end
34
+
35
+ private
36
+
37
+ def schema_errors(data)
38
+ schema = JSONSchemer.schema(Pathname.new(SCHEMA_PATH))
39
+ schema.validate(data).map do |error|
40
+ "#{error['data_pointer']}: #{error['type']}"
41
+ end
42
+ end
43
+
44
+ def security_errors(data)
45
+ errors = []
46
+ steps = data.fetch('steps', [])
47
+
48
+ errors << "Workflow exceeds maximum step count (#{MAX_STEPS})" if steps.size > MAX_STEPS
49
+
50
+ steps.each_with_index do |step, idx|
51
+ errors += validate_step(step, idx)
52
+ end
53
+
54
+ errors
55
+ end
56
+
57
+ def validate_step(step, idx)
58
+ errors = []
59
+ case step['type']
60
+ when 'shell'
61
+ unless step['command'].is_a?(Array)
62
+ errors << "Step #{idx + 1} (#{step['name']}): shell command must be an array"
63
+ end
64
+ when 'prompt'
65
+ errors << "Step #{idx + 1} (#{step['name']}): prompt is required" unless step['prompt']
66
+ end
67
+ errors
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,61 @@
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_relative 'steps/shell_step'
7
+ require_relative 'steps/rai_step'
8
+ require_relative 'steps/prompt_step'
9
+
10
+ module RosettAi
11
+ module Workflow
12
+ # Dispatches workflow step definitions to the appropriate step class.
13
+ #
14
+ # Maps step type strings to step implementations and handles
15
+ # step-level error recovery (on_failure: continue vs stop).
16
+ #
17
+ # @author hugo
18
+ # @author claude
19
+ class StepRunner
20
+ STEP_TYPES = {
21
+ 'shell' => Steps::ShellStep,
22
+ 'rai' => Steps::RaiStep,
23
+ 'prompt' => Steps::PromptStep
24
+ }.freeze
25
+
26
+ # Executes a single step definition.
27
+ #
28
+ # @param step_def [Hash] step definition from workflow YAML
29
+ # @return [Hash] execution result with :status, :message, :duration_ms
30
+ # @raise [RosettAi::WorkflowError] if step type is unknown
31
+ def run(step_def)
32
+ step_class = resolve_step_class(step_def)
33
+ step = step_class.new(step_def)
34
+ step.execute
35
+ rescue RosettAi::WorkflowError
36
+ raise
37
+ rescue StandardError => e
38
+ { status: 'fail', message: "Step '#{step_def['name']}' raised: #{e.message}", duration_ms: 0.0 }
39
+ end
40
+
41
+ # Returns a description string for dry-run mode.
42
+ #
43
+ # @param step_def [Hash] step definition from workflow YAML
44
+ # @return [String] human-readable step description
45
+ def describe(step_def)
46
+ step_class = resolve_step_class(step_def)
47
+ step = step_class.new(step_def)
48
+ step.describe
49
+ end
50
+
51
+ private
52
+
53
+ def resolve_step_class(step_def)
54
+ type = step_def.fetch('type')
55
+ STEP_TYPES.fetch(type) do
56
+ raise RosettAi::WorkflowError, "Unknown step type '#{type}' in step '#{step_def['name']}'"
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-License-Identifier: GPL-3.0-only
4
+ # Copyright (C) 2026 Hugo Antonio Sepulveda Manriquez / NeatNerds
5
+
6
+ module RosettAi
7
+ module Workflow
8
+ module Steps
9
+ # Sends a prompt to an AI engine and captures the response.
10
+ #
11
+ # Requires network connectivity unless stubbed. Currently a stub
12
+ # implementation that records the prompt text — engine integration
13
+ # will be wired when MCP/engine APIs are available.
14
+ #
15
+ # @author hugo
16
+ # @author claude
17
+ class PromptStep
18
+ # @param definition [Hash] step definition from workflow YAML
19
+ def initialize(definition)
20
+ @definition = definition
21
+ @prompt = definition.fetch('prompt')
22
+ @engine = definition['engine']
23
+ validate!
24
+ end
25
+
26
+ # @return [String] step description for dry-run
27
+ def describe
28
+ engine_label = @engine ? " (engine: #{@engine})" : ''
29
+ "[prompt] #{@definition['name']}#{engine_label} — #{truncate(@prompt, 60)}"
30
+ end
31
+
32
+ # Executes the prompt step.
33
+ #
34
+ # Currently a stub: records the prompt and returns a pass result.
35
+ # Engine integration will dispatch to the configured engine's API.
36
+ #
37
+ # @return [Hash] execution result with :status and :message
38
+ def execute
39
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
40
+ elapsed = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000).round(1)
41
+
42
+ { status: 'pass', message: "Prompt recorded: #{truncate(@prompt, 80)}", duration_ms: elapsed }
43
+ end
44
+
45
+ private
46
+
47
+ def validate!
48
+ return if @prompt.is_a?(String)
49
+
50
+ raise RosettAi::WorkflowError,
51
+ "Prompt step '#{@definition['name']}' requires a prompt string"
52
+ end
53
+
54
+ def truncate(text, max_length)
55
+ return text if text.length <= max_length
56
+
57
+ "#{text[0, max_length - 3]}..."
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end