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.
- checksums.yaml +7 -0
- data/.ai-provenance.yml +119 -0
- data/.debride_whitelist +186 -0
- data/.fasterer.yml +29 -0
- data/.mdl_style.rb +10 -0
- data/.mdlrc +3 -0
- data/.mutant.yml +49 -0
- data/.namespace-allowlist +42 -0
- data/.reek.yml +1040 -0
- data/.rosett-ai/config.yml +3 -0
- data/.rspec +5 -0
- data/.rubocop.yml +380 -0
- data/.ruby-version +1 -0
- data/.yamllint +51 -0
- data/.yardopts +12 -0
- data/AI-DISCLOSURE.md +48 -0
- data/CHANGELOG.md +519 -0
- data/CLAUDE.md +141 -0
- data/CONTRIBUTING.md +734 -0
- data/INSTALL.md +154 -0
- data/LICENSE +674 -0
- data/LICENSE.md +675 -0
- data/QUICKSTART.md +73 -0
- data/README.md +366 -0
- data/Rakefile +200 -0
- data/SECURITY.md +114 -0
- data/bin/rai +1 -0
- data/cliff.toml +52 -0
- data/conf/adopt_redactions.yml +8 -0
- data/conf/behaviour/.gitkeep +0 -0
- data/conf/compliance/cra_rules.yml +25 -0
- data/conf/compliance/license_rules.yml +20 -0
- data/conf/design/aaif_alignment.yml +181 -0
- data/conf/design/ab_testing.yml +172 -0
- data/conf/design/accessibility.yml +84 -0
- data/conf/design/ai_authorship.yml +210 -0
- data/conf/design/ai_provenance.yml +224 -0
- data/conf/design/ai_tool_configuration.yml +207 -0
- data/conf/design/architecture.yml +139 -0
- data/conf/design/autocompletion.yml +115 -0
- data/conf/design/backward_compatibility.yml +112 -0
- data/conf/design/behaviour_composition.yml +246 -0
- data/conf/design/build_rake_extraction.yml +57 -0
- data/conf/design/ci_pipeline.yml +100 -0
- data/conf/design/claude_code_configuration.yml +157 -0
- data/conf/design/compiler.yml +128 -0
- data/conf/design/comply.yml +153 -0
- data/conf/design/content_packs.yml +84 -0
- data/conf/design/desktop_integration.yml +289 -0
- data/conf/design/distribution.yml +216 -0
- data/conf/design/doctor.yml +184 -0
- data/conf/design/documentation.yml +152 -0
- data/conf/design/engine_architecture.yml +257 -0
- data/conf/design/error_handling.yml +103 -0
- data/conf/design/feature_flags.yml +142 -0
- data/conf/design/git_hooks.yml +165 -0
- data/conf/design/gui_plugins.yml +475 -0
- data/conf/design/i18n.yml +84 -0
- data/conf/design/integration_testing.yml +56 -0
- data/conf/design/licensing_system.yml +88 -0
- data/conf/design/lifecycle_management.yml +208 -0
- data/conf/design/mcp_integration.yml +207 -0
- data/conf/design/mcp_settings.yml +126 -0
- data/conf/design/migration.yml +56 -0
- data/conf/design/monitoring_observability.yml +194 -0
- data/conf/design/namespace_cleanup.yml +145 -0
- data/conf/design/plugin_test_segregation.yml +145 -0
- data/conf/design/policy_management.yml +229 -0
- data/conf/design/project_management.yml +183 -0
- data/conf/design/rai_mcp_asset_discovery.yml +164 -0
- data/conf/design/rai_mcp_server.yml +605 -0
- data/conf/design/release_management.yml +117 -0
- data/conf/design/retrofit.yml +199 -0
- data/conf/design/retrospective_analyzer.yml +79 -0
- data/conf/design/scope_hierarchy.yml +352 -0
- data/conf/design/security.yml +115 -0
- data/conf/design/session_retrospective.yml +85 -0
- data/conf/design/smart_ui_feedback.yml +89 -0
- data/conf/design/structured_logging.yml +148 -0
- data/conf/design/styles.yml +123 -0
- data/conf/design/test_peer_review.yml +89 -0
- data/conf/design/testing.yml +136 -0
- data/conf/design/threat_model.yml +108 -0
- data/conf/design/ui_framework.yml +111 -0
- data/conf/design/usage_optimization.yml +122 -0
- data/conf/design/version_management.yml +60 -0
- data/conf/design/workflow.yml +227 -0
- data/conf/mcp/server_defaults.yml +42 -0
- data/conf/mcp/trust.yml +21 -0
- data/conf/packaging/core.yml +12 -0
- data/conf/packaging/gtk4.yml +11 -0
- data/conf/packaging/qt6.yml +11 -0
- data/conf/policy/default_deny_list.yml +197 -0
- data/conf/review/cli-command-audit.yml +857 -0
- data/conf/review/design-docs.yml +1064 -0
- data/conf/review/design-questionnaire.yml +153 -0
- data/conf/review/questionnaire.yml +146 -0
- data/conf/review/rosett-ai-core.yml +2919 -0
- data/conf/schemas/ai_config_schema.json +73 -0
- data/conf/schemas/behaviour_schema.json +132 -0
- data/conf/schemas/compliance_rule_schema.json +63 -0
- data/conf/schemas/content_pack_manifest_schema.json +51 -0
- data/conf/schemas/design_schema.json +210 -0
- data/conf/schemas/engine_manifest_schema.json +144 -0
- data/conf/schemas/lockfile_schema.json +74 -0
- data/conf/schemas/mcp_server_schema.json +48 -0
- data/conf/schemas/packaging_schema.json +70 -0
- data/conf/schemas/policy_schema.json +85 -0
- data/conf/schemas/provenance_schema.json +84 -0
- data/conf/schemas/rai_config_schema.json +56 -0
- data/conf/schemas/rai_project_schema.json +20 -0
- data/conf/schemas/scope_hierarchy_schema.json +49 -0
- data/conf/schemas/target_schema.json +67 -0
- data/conf/schemas/tooling_schema.json +65 -0
- data/conf/schemas/workflow_schema.json +112 -0
- data/conf/targets/agents_md.yml +17 -0
- data/conf/targets/claude.yml +12 -0
- data/conf/tooling/tools.yml +58 -0
- data/dist/rosett-ai-mcp.service +48 -0
- data/dist/rosett-ai-mcp.yml.default +45 -0
- data/doc/AAIF_POSITIONING.md +58 -0
- data/doc/ADOPT.md +224 -0
- data/doc/AI_PROVENANCE.md +139 -0
- data/doc/ARCHITECTURE.md +920 -0
- data/doc/BEHAVIOUR.md +409 -0
- data/doc/BUILD.md +138 -0
- data/doc/CI_CD_RECIPES.md +171 -0
- data/doc/CLAUDE_SESSIONS_MOVED.md +16 -0
- data/doc/COMMAND_ANALYSIS.md +229 -0
- data/doc/CONFIGURATION.md +281 -0
- data/doc/DESIGN_AUDIT.md +235 -0
- data/doc/DESIGN_PEER_REVIEW.md +771 -0
- data/doc/DESKTOP.md +447 -0
- data/doc/ENGINES.md +567 -0
- data/doc/ENGINE_DEVELOPMENT_GUIDE.md +417 -0
- data/doc/FEATURE_AUDIT.md +218 -0
- data/doc/IMPLEMENTATION_PLAN.md +669 -0
- data/doc/INCIDENT_REPORT_2026-02-02.md +251 -0
- data/doc/MIGRATION_GUIDE.md +88 -0
- data/doc/PACKAGING.md +232 -0
- data/doc/PROJECT_DASHBOARD.md +153 -0
- data/doc/PULP_DEPLOYMENT.md +164 -0
- data/doc/QUALITY_FIX_SUMMARY.md +110 -0
- data/doc/QUICK_START.md +162 -0
- data/doc/REEK_CONFIGURATION.md +166 -0
- data/doc/REFERENCE.md +253 -0
- data/doc/REFERENCES.md +324 -0
- data/doc/SECURITY_REVIEW_CHECKLIST.md +72 -0
- data/doc/SESSION_2026-02-28_GTK4_HARDENING.md +359 -0
- data/doc/SETUP.md +202 -0
- data/doc/TEST_PEER_REVIEW.md +152 -0
- data/doc/THREAT_MODEL.md +230 -0
- data/doc/USAGE.md +545 -0
- data/doc/USER_MANUAL.md +585 -0
- data/doc/ai_test_review_checklist.md +110 -0
- data/doc/changes/2026-02-18-packaging-fpm.md +155 -0
- data/doc/changes/2026-02-19-testing-infrastructure.md +221 -0
- data/doc/changes/2026-02-20-security-implementation.md +281 -0
- data/doc/changes/2026-02-20-styles-implementation.md +220 -0
- data/doc/changes/2026-02-21-architecture-completion.md +95 -0
- data/doc/changes/2026-02-21-architecture-ui-layer.md +253 -0
- data/doc/changes/2026-02-21-cc-config-implementation.md +108 -0
- data/doc/changes/2026-02-21-ci-pipeline-implementation.md +214 -0
- data/doc/changes/2026-02-21-compiler-multi-target-pipeline.md +241 -0
- data/doc/changes/2026-02-21-config-design-show-commands.md +61 -0
- data/doc/changes/2026-02-21-design-implementation-overview.md +455 -0
- data/doc/changes/2026-02-21-lifecycle-management.md +196 -0
- data/doc/changes/2026-02-21-path-resolver.md +128 -0
- data/doc/changes/2026-02-24-ci-tmpdir-mutant-fetch.md +45 -0
- data/doc/changes/2026-03-01-ci-bundler-strategy.md +120 -0
- data/doc/changes/2026-03-20-security-hardening-phase2.md +163 -0
- data/doc/context/SESSION-HANDOFF.md +69 -0
- data/doc/context/ai-engine-usage-trends-2026.md +80 -0
- data/doc/context/plan-pluggable-engines.md +590 -0
- data/doc/decisions/001-flog-deferred.md +32 -0
- data/doc/decisions/002-path-resolution-strategy.md +158 -0
- data/doc/decisions/003-ui-adapter-selection.md +193 -0
- data/doc/decisions/004-design-document-validation.md +179 -0
- data/doc/decisions/005-package-splitting-strategy.md +200 -0
- data/doc/decisions/006-multi-engine-architecture.md +147 -0
- data/doc/decisions/007-engine-agnostic-pivot.md +219 -0
- data/doc/decisions/008-ci-bundler-strategy.md +129 -0
- data/doc/decisions/009-core-only-v1-release.md +60 -0
- data/doc/decisions/010-engine-debian-packaging.md +66 -0
- data/doc/decisions/011-context-aware-cli.md +71 -0
- data/doc/dependency_decisions.yml +247 -0
- data/doc/issues/001-wrapper-missing-environment-variables.md +197 -0
- data/doc/issues/002-embedded-ruby-wrong-prefix.md +217 -0
- data/doc/issues/003-smoke-test-false-positive.md +127 -0
- data/doc/issues/004-market-research-design-updates.md +109 -0
- data/doc/issues/005-compile-scope-coexistence.md +161 -0
- data/doc/locales/.gitkeep +0 -0
- data/doc/man/rai.1.ronn +505 -0
- data/doc/operations/packaging.md +133 -0
- data/doc/operations/rosett-ai-release.md +65 -0
- data/doc/reference/error-catalog.md +107 -0
- data/doc/reference/rosett-ai-technical-reference.pdf +0 -0
- data/doc/reference/src/Pictures/cover.jpg +0 -0
- data/doc/reference/src/Pictures/head1.jpg +0 -0
- data/doc/reference/src/Pictures/head2.jpg +0 -0
- data/doc/reference/src/Pictures/head3.jpg +0 -0
- data/doc/reference/src/Pictures/head4.jpg +0 -0
- data/doc/reference/src/Pictures/head5.jpg +0 -0
- data/doc/reference/src/Pictures/head6.jpg +0 -0
- data/doc/reference/src/Pictures/head7.jpg +0 -0
- data/doc/reference/src/Pictures/head8.jpg +0 -0
- data/doc/reference/src/StyleInd.ist +4 -0
- data/doc/reference/src/bibliography.bib +79 -0
- data/doc/reference/src/main.tex +1288 -0
- data/doc/reference/src/structure.tex +303 -0
- data/doc/rosett-ai-bookmarks.html +301 -0
- data/kitchen.yml +46 -0
- data/lib/rosett_ai/adopter/executor_resolver.rb +77 -0
- data/lib/rosett_ai/adopter/local_analysis_collector.rb +154 -0
- data/lib/rosett_ai/adopter/rule_adopter.rb +254 -0
- data/lib/rosett_ai/ai_config/config_compiler.rb +111 -0
- data/lib/rosett_ai/ai_config/context_window.rb +55 -0
- data/lib/rosett_ai/ai_config/cost_controls.rb +44 -0
- data/lib/rosett_ai/ai_config/fallback_chain.rb +64 -0
- data/lib/rosett_ai/ai_config/model_router.rb +121 -0
- data/lib/rosett_ai/ai_config/validator.rb +45 -0
- data/lib/rosett_ai/authorship/attribution_compiler.rb +99 -0
- data/lib/rosett_ai/authorship/disclosure_policy.rb +81 -0
- data/lib/rosett_ai/authorship/review_validator.rb +39 -0
- data/lib/rosett_ai/authorship/trailer_generator.rb +88 -0
- data/lib/rosett_ai/backup/compressor.rb +180 -0
- data/lib/rosett_ai/backup/destination.rb +91 -0
- data/lib/rosett_ai/behaviour/manager.rb +156 -0
- data/lib/rosett_ai/compiler/backend.rb +86 -0
- data/lib/rosett_ai/compiler/backends/agents_md_backend.rb +80 -0
- data/lib/rosett_ai/compiler/backends/claude_backend.rb +88 -0
- data/lib/rosett_ai/compiler/backends/generic_backend.rb +15 -0
- data/lib/rosett_ai/compiler/behaviour_compiler.rb +40 -0
- data/lib/rosett_ai/compiler/capability_checker.rb +104 -0
- data/lib/rosett_ai/compiler/compilation_pipeline.rb +361 -0
- data/lib/rosett_ai/compiler/compiled_output.rb +39 -0
- data/lib/rosett_ai/compiler/locale_compiler.rb +250 -0
- data/lib/rosett_ai/compiler/target_profile.rb +112 -0
- data/lib/rosett_ai/completion/generator.rb +101 -0
- data/lib/rosett_ai/completion/shells/bash_generator.rb +126 -0
- data/lib/rosett_ai/completion/shells/fish_generator.rb +78 -0
- data/lib/rosett_ai/completion/shells/zsh_generator.rb +126 -0
- data/lib/rosett_ai/comply/checkers/cra_checker.rb +102 -0
- data/lib/rosett_ai/comply/checkers/license_checker.rb +85 -0
- data/lib/rosett_ai/comply/checkers/spdx_header_checker.rb +98 -0
- data/lib/rosett_ai/comply/reporter.rb +113 -0
- data/lib/rosett_ai/comply/runner.rb +50 -0
- data/lib/rosett_ai/composition/circular_dependency_detector.rb +56 -0
- data/lib/rosett_ai/composition/composer.rb +158 -0
- data/lib/rosett_ai/composition/composition_result.rb +64 -0
- data/lib/rosett_ai/composition/conflict_detector.rb +53 -0
- data/lib/rosett_ai/composition/lockfile.rb +103 -0
- data/lib/rosett_ai/composition/merge_strategy.rb +131 -0
- data/lib/rosett_ai/composition/priority_sorter.rb +29 -0
- data/lib/rosett_ai/composition/scope_resolver.rb +55 -0
- data/lib/rosett_ai/config/compile_result.rb +37 -0
- data/lib/rosett_ai/config/compiler.rb +13 -0
- data/lib/rosett_ai/config/domain_transformer.rb +13 -0
- data/lib/rosett_ai/config/key_map.rb +13 -0
- data/lib/rosett_ai/config/masking_secret_resolver.rb +40 -0
- data/lib/rosett_ai/config/scope_router.rb +13 -0
- data/lib/rosett_ai/config/secret_resolver.rb +125 -0
- data/lib/rosett_ai/configuration.rb +119 -0
- data/lib/rosett_ai/content/content_client.rb +60 -0
- data/lib/rosett_ai/content/pack_installer.rb +117 -0
- data/lib/rosett_ai/content/pack_manifest.rb +50 -0
- data/lib/rosett_ai/content/pack_registry.rb +68 -0
- data/lib/rosett_ai/content_packs/manager.rb +50 -0
- data/lib/rosett_ai/dbus/compositor_detector.rb +77 -0
- data/lib/rosett_ai/dbus/focus_adapters/base.rb +59 -0
- data/lib/rosett_ai/dbus/focus_adapters/gnome_adapter.rb +172 -0
- data/lib/rosett_ai/dbus/focus_adapters/hyprland_adapter.rb +77 -0
- data/lib/rosett_ai/dbus/focus_adapters/i3_adapter.rb +65 -0
- data/lib/rosett_ai/dbus/focus_adapters/kwin_adapter.rb +103 -0
- data/lib/rosett_ai/dbus/focus_adapters/x11_adapter.rb +105 -0
- data/lib/rosett_ai/dbus/focus_monitor_interface.rb +103 -0
- data/lib/rosett_ai/dbus/manager_interface.rb +213 -0
- data/lib/rosett_ai/dbus/plugin_manager_interface.rb +169 -0
- data/lib/rosett_ai/dbus/rate_limiter.rb +89 -0
- data/lib/rosett_ai/dbus/service.rb +121 -0
- data/lib/rosett_ai/dbus/status_notifier_interface.rb +79 -0
- data/lib/rosett_ai/deprecation.rb +79 -0
- data/lib/rosett_ai/desktop/dbus_client.rb +259 -0
- data/lib/rosett_ai/desktop/gtk4_app.rb +371 -0
- data/lib/rosett_ai/desktop/gtk4_preferences.rb +331 -0
- data/lib/rosett_ai/desktop/gui_logger.rb +236 -0
- data/lib/rosett_ai/doctor/check.rb +92 -0
- data/lib/rosett_ai/doctor/checks/cache_health_check.rb +50 -0
- data/lib/rosett_ai/doctor/checks/dbus_availability_check.rb +39 -0
- data/lib/rosett_ai/doctor/checks/engine_detection_check.rb +46 -0
- data/lib/rosett_ai/doctor/checks/file_permission_check.rb +44 -0
- data/lib/rosett_ai/doctor/checks/gem_dependency_check.rb +55 -0
- data/lib/rosett_ai/doctor/checks/ruby_version_check.rb +50 -0
- data/lib/rosett_ai/doctor/checks/stale_config_nncc_check.rb +57 -0
- data/lib/rosett_ai/doctor/checks/stale_home_nncc_check.rb +59 -0
- data/lib/rosett_ai/doctor.rb +81 -0
- data/lib/rosett_ai/documentation/reference_compiler.rb +122 -0
- data/lib/rosett_ai/documentation/translator.rb +62 -0
- data/lib/rosett_ai/engines/base_config_compiler.rb +203 -0
- data/lib/rosett_ai/engines/detector.rb +63 -0
- data/lib/rosett_ai/engines/registry.rb +50 -0
- data/lib/rosett_ai/error_handler.rb +139 -0
- data/lib/rosett_ai/exit_codes.rb +76 -0
- data/lib/rosett_ai/feature_flags.rb +102 -0
- data/lib/rosett_ai/formatting.rb +33 -0
- data/lib/rosett_ai/gem_consistency_checker.rb +199 -0
- data/lib/rosett_ai/git_hooks/chain_detector.rb +86 -0
- data/lib/rosett_ai/git_hooks/installer.rb +175 -0
- data/lib/rosett_ai/git_hooks/script_generator.rb +125 -0
- data/lib/rosett_ai/gitlab/validators/supplementary_gitlab_ci_yaml_validator.rb +79 -0
- data/lib/rosett_ai/i18n/locale_resolver.rb +46 -0
- data/lib/rosett_ai/i18n/utf8_checker.rb +32 -0
- data/lib/rosett_ai/init/config_file_writer.rb +24 -0
- data/lib/rosett_ai/init/directory_builder.rb +38 -0
- data/lib/rosett_ai/init/file_copier.rb +95 -0
- data/lib/rosett_ai/init/global_initializer.rb +28 -0
- data/lib/rosett_ai/init/local_initializer.rb +27 -0
- data/lib/rosett_ai/init/mcp_registrar.rb +109 -0
- data/lib/rosett_ai/init/project_initializer.rb +38 -0
- data/lib/rosett_ai/licensing/license_key.rb +139 -0
- data/lib/rosett_ai/licensing/license_store.rb +64 -0
- data/lib/rosett_ai/licensing/license_validator.rb +60 -0
- data/lib/rosett_ai/licensing/tier.rb +42 -0
- data/lib/rosett_ai/mcp/admin/auditor.rb +88 -0
- data/lib/rosett_ai/mcp/admin/health_checker.rb +81 -0
- data/lib/rosett_ai/mcp/admin/registry.rb +100 -0
- data/lib/rosett_ai/mcp/admin/schema_validator.rb +63 -0
- data/lib/rosett_ai/mcp/enforcement/.gitkeep +0 -0
- data/lib/rosett_ai/mcp/enforcement/hook_generator.rb +197 -0
- data/lib/rosett_ai/mcp/enforcement/validator.rb +215 -0
- data/lib/rosett_ai/mcp/governance.rb +160 -0
- data/lib/rosett_ai/mcp/http_security_config.rb +158 -0
- data/lib/rosett_ai/mcp/instructions.rb +266 -0
- data/lib/rosett_ai/mcp/key_hasher.rb +66 -0
- data/lib/rosett_ai/mcp/keyfile.rb +221 -0
- data/lib/rosett_ai/mcp/middleware/authentication.rb +146 -0
- data/lib/rosett_ai/mcp/middleware/content_type.rb +56 -0
- data/lib/rosett_ai/mcp/middleware/cors.rb +83 -0
- data/lib/rosett_ai/mcp/middleware/origin_validation.rb +73 -0
- data/lib/rosett_ai/mcp/middleware/rate_limit.rb +106 -0
- data/lib/rosett_ai/mcp/middleware/request_size.rb +51 -0
- data/lib/rosett_ai/mcp/plugins.rb +143 -0
- data/lib/rosett_ai/mcp/prompts/compilation_prompt.rb +40 -0
- data/lib/rosett_ai/mcp/prompts/compliance_prompt.rb +41 -0
- data/lib/rosett_ai/mcp/prompts/diagnostics_prompt.rb +41 -0
- data/lib/rosett_ai/mcp/prompts/validation_prompt.rb +41 -0
- data/lib/rosett_ai/mcp/resources/behaviour_resource.rb +127 -0
- data/lib/rosett_ai/mcp/resources/config_resource.rb +72 -0
- data/lib/rosett_ai/mcp/resources/design_resource.rb +58 -0
- data/lib/rosett_ai/mcp/resources/hooks_resource.rb +74 -0
- data/lib/rosett_ai/mcp/resources/provenance_resource.rb +51 -0
- data/lib/rosett_ai/mcp/resources/rules_resource.rb +60 -0
- data/lib/rosett_ai/mcp/resources/schema_resource.rb +72 -0
- data/lib/rosett_ai/mcp/response_helper.rb +46 -0
- data/lib/rosett_ai/mcp/security_logger.rb +60 -0
- data/lib/rosett_ai/mcp/server.rb +212 -0
- data/lib/rosett_ai/mcp/settings/server_installer.rb +112 -0
- data/lib/rosett_ai/mcp/settings/trust_manager.rb +142 -0
- data/lib/rosett_ai/mcp/tools/adopt_tool.rb +70 -0
- data/lib/rosett_ai/mcp/tools/backup_tool.rb +64 -0
- data/lib/rosett_ai/mcp/tools/behaviour_display_tool.rb +72 -0
- data/lib/rosett_ai/mcp/tools/behaviour_list_tool.rb +56 -0
- data/lib/rosett_ai/mcp/tools/behaviour_manage_tool.rb +114 -0
- data/lib/rosett_ai/mcp/tools/behaviour_show_tool.rb +62 -0
- data/lib/rosett_ai/mcp/tools/compile_status_tool.rb +122 -0
- data/lib/rosett_ai/mcp/tools/compile_tool.rb +191 -0
- data/lib/rosett_ai/mcp/tools/comply_tool.rb +79 -0
- data/lib/rosett_ai/mcp/tools/config_compile_tool.rb +71 -0
- data/lib/rosett_ai/mcp/tools/config_status_tool.rb +79 -0
- data/lib/rosett_ai/mcp/tools/content_tool.rb +78 -0
- data/lib/rosett_ai/mcp/tools/context_query_tool.rb +156 -0
- data/lib/rosett_ai/mcp/tools/design_list_tool.rb +57 -0
- data/lib/rosett_ai/mcp/tools/design_show_tool.rb +69 -0
- data/lib/rosett_ai/mcp/tools/doctor_tool.rb +62 -0
- data/lib/rosett_ai/mcp/tools/documentation_status_tool.rb +45 -0
- data/lib/rosett_ai/mcp/tools/engines_tool.rb +84 -0
- data/lib/rosett_ai/mcp/tools/hook_install_tool.rb +190 -0
- data/lib/rosett_ai/mcp/tools/hook_preview_tool.rb +173 -0
- data/lib/rosett_ai/mcp/tools/hooks_status_tool.rb +84 -0
- data/lib/rosett_ai/mcp/tools/init_tool.rb +87 -0
- data/lib/rosett_ai/mcp/tools/license_status_tool.rb +44 -0
- data/lib/rosett_ai/mcp/tools/project_tool.rb +117 -0
- data/lib/rosett_ai/mcp/tools/provenance_tool.rb +97 -0
- data/lib/rosett_ai/mcp/tools/provenance_write_tool.rb +40 -0
- data/lib/rosett_ai/mcp/tools/retrofit_tool.rb +81 -0
- data/lib/rosett_ai/mcp/tools/rule_search_tool.rb +163 -0
- data/lib/rosett_ai/mcp/tools/schema_get_tool.rb +94 -0
- data/lib/rosett_ai/mcp/tools/tooling_tool.rb +86 -0
- data/lib/rosett_ai/mcp/tools/validate_tool.rb +105 -0
- data/lib/rosett_ai/mcp/tools/workflow_execute_tool.rb +74 -0
- data/lib/rosett_ai/mcp/tools/workflow_tool.rb +78 -0
- data/lib/rosett_ai/migration/detector.rb +117 -0
- data/lib/rosett_ai/migration/nncc_config_migrator.rb +94 -0
- data/lib/rosett_ai/migration/nncc_project_migrator.rb +90 -0
- data/lib/rosett_ai/migration/xdg_migrator.rb +123 -0
- data/lib/rosett_ai/package_manager/apt.rb +108 -0
- data/lib/rosett_ai/package_manager/base.rb +68 -0
- data/lib/rosett_ai/package_manager/gem_backend.rb +90 -0
- data/lib/rosett_ai/packaging/variant_config.rb +92 -0
- data/lib/rosett_ai/path_resolver.rb +115 -0
- data/lib/rosett_ai/plugins/contract.rb +43 -0
- data/lib/rosett_ai/plugins/engine_contract.rb +60 -0
- data/lib/rosett_ai/plugins/gui_contract.rb +74 -0
- data/lib/rosett_ai/plugins/mcp_contract.rb +48 -0
- data/lib/rosett_ai/plugins/registry.rb +150 -0
- data/lib/rosett_ai/policy/auditor.rb +41 -0
- data/lib/rosett_ai/policy/deny_list.rb +71 -0
- data/lib/rosett_ai/policy/opt_out_scanner.rb +37 -0
- data/lib/rosett_ai/policy/policy_compiler.rb +84 -0
- data/lib/rosett_ai/policy/protected_files.rb +47 -0
- data/lib/rosett_ai/policy/tier_hierarchy.rb +48 -0
- data/lib/rosett_ai/policy/validator.rb +35 -0
- data/lib/rosett_ai/profiler.rb +79 -0
- data/lib/rosett_ai/project/drift_detector.rb +126 -0
- data/lib/rosett_ai/project/manager.rb +115 -0
- data/lib/rosett_ai/project/sync_manager.rb +138 -0
- data/lib/rosett_ai/project/template_applier.rb +105 -0
- data/lib/rosett_ai/project_context.rb +82 -0
- data/lib/rosett_ai/provenance/entry.rb +63 -0
- data/lib/rosett_ai/provenance/file_source.rb +32 -0
- data/lib/rosett_ai/provenance/source.rb +62 -0
- data/lib/rosett_ai/provenance/store.rb +153 -0
- data/lib/rosett_ai/provenance/tracker.rb +62 -0
- data/lib/rosett_ai/provenance/trailer_generator.rb +43 -0
- data/lib/rosett_ai/provenance/validator.rb +45 -0
- data/lib/rosett_ai/quorum/collector.rb +59 -0
- data/lib/rosett_ai/quorum/comparator.rb +81 -0
- data/lib/rosett_ai/quorum/dispatcher.rb +57 -0
- data/lib/rosett_ai/quorum/strategies/adopt.rb +56 -0
- data/lib/rosett_ai/rai_config.rb +107 -0
- data/lib/rosett_ai/retrofit/base_parser.rb +66 -0
- data/lib/rosett_ai/retrofit/engine.rb +171 -0
- data/lib/rosett_ai/retrofit/parsers/agents_md_parser.rb +50 -0
- data/lib/rosett_ai/retrofit/parsers/claude_parser.rb +69 -0
- data/lib/rosett_ai/retrofit/parsers/cursor_parser.rb +82 -0
- data/lib/rosett_ai/retrofit/round_trip_validator.rb +65 -0
- data/lib/rosett_ai/retrofit/scanner.rb +47 -0
- data/lib/rosett_ai/retrofit/secret_detector.rb +87 -0
- data/lib/rosett_ai/secrets_resolver.rb +71 -0
- data/lib/rosett_ai/smart_feedback/suggester.rb +83 -0
- data/lib/rosett_ai/smart_feedback/thor_middleware.rb +84 -0
- data/lib/rosett_ai/structured_logger.rb +110 -0
- data/lib/rosett_ai/telemetry/json_lines_writer.rb +50 -0
- data/lib/rosett_ai/telemetry/log_rotator.rb +67 -0
- data/lib/rosett_ai/telemetry/provider.rb +26 -0
- data/lib/rosett_ai/telemetry/reporter.rb +144 -0
- data/lib/rosett_ai/telemetry.rb +47 -0
- data/lib/rosett_ai/text_sanitizer.rb +62 -0
- data/lib/rosett_ai/thor/cli.rb +269 -0
- data/lib/rosett_ai/thor/tasks/adopt.rb +250 -0
- data/lib/rosett_ai/thor/tasks/backup.rb +420 -0
- data/lib/rosett_ai/thor/tasks/behaviour.rb +474 -0
- data/lib/rosett_ai/thor/tasks/build.rb +1162 -0
- data/lib/rosett_ai/thor/tasks/compile.rb +415 -0
- data/lib/rosett_ai/thor/tasks/completion.rb +123 -0
- data/lib/rosett_ai/thor/tasks/comply.rb +82 -0
- data/lib/rosett_ai/thor/tasks/config.rb +265 -0
- data/lib/rosett_ai/thor/tasks/content.rb +193 -0
- data/lib/rosett_ai/thor/tasks/dbus.rb +321 -0
- data/lib/rosett_ai/thor/tasks/design.rb +258 -0
- data/lib/rosett_ai/thor/tasks/desktop.rb +129 -0
- data/lib/rosett_ai/thor/tasks/doctor.rb +127 -0
- data/lib/rosett_ai/thor/tasks/documentation.rb +321 -0
- data/lib/rosett_ai/thor/tasks/engines.rb +167 -0
- data/lib/rosett_ai/thor/tasks/hooks.rb +219 -0
- data/lib/rosett_ai/thor/tasks/init.rb +259 -0
- data/lib/rosett_ai/thor/tasks/license.rb +120 -0
- data/lib/rosett_ai/thor/tasks/mcp.rb +535 -0
- data/lib/rosett_ai/thor/tasks/migrate.rb +121 -0
- data/lib/rosett_ai/thor/tasks/plugins.rb +157 -0
- data/lib/rosett_ai/thor/tasks/project.rb +260 -0
- data/lib/rosett_ai/thor/tasks/provenance.rb +195 -0
- data/lib/rosett_ai/thor/tasks/release.rb +314 -0
- data/lib/rosett_ai/thor/tasks/retrofit.rb +90 -0
- data/lib/rosett_ai/thor/tasks/tooling.rb +308 -0
- data/lib/rosett_ai/thor/tasks/validate.rb +108 -0
- data/lib/rosett_ai/thor/tasks/workflow.rb +196 -0
- data/lib/rosett_ai/tooling/ci_yaml_validator.rb +37 -0
- data/lib/rosett_ai/tooling/version_checker.rb +35 -0
- data/lib/rosett_ai/ui/accessible_tui.rb +61 -0
- data/lib/rosett_ai/ui/base.rb +46 -0
- data/lib/rosett_ai/ui/gtk4.rb +98 -0
- data/lib/rosett_ai/ui/kde.rb +40 -0
- data/lib/rosett_ai/ui/qt6.rb +40 -0
- data/lib/rosett_ai/ui/registry.rb +60 -0
- data/lib/rosett_ai/ui/tty_helper.rb +74 -0
- data/lib/rosett_ai/ui/tui.rb +59 -0
- data/lib/rosett_ai/validators/behaviour_validator.rb +20 -0
- data/lib/rosett_ai/validators/design_validator.rb +17 -0
- data/lib/rosett_ai/validators/schema_validator.rb +84 -0
- data/lib/rosett_ai/validators/tooling_validator.rb +17 -0
- data/lib/rosett_ai/version.rb +8 -0
- data/lib/rosett_ai/version_consistency_checker.rb +129 -0
- data/lib/rosett_ai/workflow/audit_log.rb +86 -0
- data/lib/rosett_ai/workflow/engine.rb +142 -0
- data/lib/rosett_ai/workflow/manager.rb +82 -0
- data/lib/rosett_ai/workflow/schema_validator.rb +71 -0
- data/lib/rosett_ai/workflow/step_runner.rb +61 -0
- data/lib/rosett_ai/workflow/steps/prompt_step.rb +62 -0
- data/lib/rosett_ai/workflow/steps/rai_step.rb +74 -0
- data/lib/rosett_ai/workflow/steps/shell_step.rb +53 -0
- data/lib/rosett_ai/yaml_loader.rb +78 -0
- data/lib/rosett_ai.rb +221 -0
- data/lib/rubocop/cop/rosett_ai/shell_interpolation.rb +54 -0
- data/lib/rubocop/cop/rosett_ai/unsafe_const_get.rb +60 -0
- data/lib/rubocop/cop/rosett_ai/unsafe_send.rb +50 -0
- data/lib/rubocop/cop/rosett_ai/unsafe_yaml_load.rb +40 -0
- data/lib/rubocop/rosett_ai.rb +9 -0
- data/lib/scripts/generated/docker_hub_tags.rb +126 -0
- data/locales/.gitkeep +0 -0
- data/locales/ar.yml +579 -0
- data/locales/en.yml +571 -0
- data/locales/fr.yml +567 -0
- data/packaging/build-engine-deb.sh +81 -0
- data/packaging/scripts/postinst +17 -0
- data/packaging/scripts/postrm +19 -0
- data/packaging/scripts/prerm +10 -0
- data/packaging/wrapper.sh.template +38 -0
- data/rosett-ai.gemspec +63 -0
- data/rules/.gitkeep +0 -0
- data/scripts/publish/pulp_upload.sh +123 -0
- data/settings.json +29 -0
- data/share/applications/be.neatnerds.rosettai.desktop +29 -0
- data/share/dbus-1/interfaces/be.neatnerds.rosettai.xml +103 -0
- data/share/dbus-1/services/be.neatnerds.rosettai.service +3 -0
- data/share/templates/behaviour/criticalthinking.yml +69 -0
- 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
|