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,43 @@
|
|
|
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 Provenance
|
|
8
|
+
# Generates commit trailers from provenance entries.
|
|
9
|
+
#
|
|
10
|
+
# Produces standardised commit trailers in the format:
|
|
11
|
+
# Role: Tool Version (Provider) <email>
|
|
12
|
+
class TrailerGenerator
|
|
13
|
+
# Generates a commit trailer from a provenance entry.
|
|
14
|
+
#
|
|
15
|
+
# @param entry [Entry, Hash] provenance entry
|
|
16
|
+
# @return [String] formatted commit trailer
|
|
17
|
+
def generate(entry)
|
|
18
|
+
entry_hash = entry.is_a?(Hash) ? entry : entry.to_h
|
|
19
|
+
role = entry_hash['ai_role'] || entry_hash[:ai_role]
|
|
20
|
+
tool = entry_hash['ai_tool'] || entry_hash[:ai_tool]
|
|
21
|
+
|
|
22
|
+
"#{role}: #{tool}"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Generates trailers for all entries in a provenance dataset.
|
|
26
|
+
#
|
|
27
|
+
# @param data [Hash] full provenance data with 'entries' key
|
|
28
|
+
# @return [Array<String>] unique trailers
|
|
29
|
+
def generate_all(data)
|
|
30
|
+
entries = data.fetch('entries', [])
|
|
31
|
+
entries.map { |entry| generate(entry) }.uniq
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Formats trailers as a block suitable for appending to a commit message.
|
|
35
|
+
#
|
|
36
|
+
# @param data [Hash] full provenance data
|
|
37
|
+
# @return [String] newline-separated trailer block
|
|
38
|
+
def trailer_block(data)
|
|
39
|
+
generate_all(data).join("\n")
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-only
|
|
4
|
+
# Copyright (C) 2026 Hugo Antonio Sepulveda Manriquez / NeatNerds
|
|
5
|
+
|
|
6
|
+
require 'json'
|
|
7
|
+
require 'json_schemer'
|
|
8
|
+
|
|
9
|
+
module RosettAi
|
|
10
|
+
module Provenance
|
|
11
|
+
# Validates provenance entries against the provenance JSON Schema.
|
|
12
|
+
class Validator
|
|
13
|
+
SCHEMA_PATH = File.join(RosettAi.root, 'conf', 'schemas', 'provenance_schema.json')
|
|
14
|
+
|
|
15
|
+
# Validates provenance data against the schema.
|
|
16
|
+
#
|
|
17
|
+
# @param data [Hash] provenance data (parsed YAML)
|
|
18
|
+
# @return [Array<String>] list of validation errors (empty if valid)
|
|
19
|
+
def validate(data)
|
|
20
|
+
schema = load_schema
|
|
21
|
+
schemer = JSONSchemer.schema(schema)
|
|
22
|
+
errors = schemer.validate(data)
|
|
23
|
+
errors.map { |error| format_error(error) }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# @param data [Hash] provenance data
|
|
27
|
+
# @return [Boolean] true if data is valid
|
|
28
|
+
def valid?(data)
|
|
29
|
+
validate(data).empty?
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def load_schema
|
|
35
|
+
JSON.parse(File.read(SCHEMA_PATH))
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def format_error(error)
|
|
39
|
+
path = error['data_pointer']
|
|
40
|
+
message = error['type']
|
|
41
|
+
"#{path}: #{message}"
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-only
|
|
4
|
+
# Copyright (C) 2026 Hugo Antonio Sepulveda Manriquez / NeatNerds
|
|
5
|
+
|
|
6
|
+
module RosettAi
|
|
7
|
+
module Quorum
|
|
8
|
+
# Normalizes per-engine responses to the standard adopt contract:
|
|
9
|
+
# { findings: [...], overall_status: "pass|warn|fail", summary: "..." }
|
|
10
|
+
#
|
|
11
|
+
# Annotates each finding with its source engine.
|
|
12
|
+
class Collector
|
|
13
|
+
# Normalize dispatched results into per-engine response hashes.
|
|
14
|
+
#
|
|
15
|
+
# @param dispatched [Hash<String, Hash>] engine_name => dispatch result
|
|
16
|
+
# @return [Hash<String, Hash>] engine_name => normalized result
|
|
17
|
+
def collect(dispatched)
|
|
18
|
+
dispatched.transform_values { |dispatch_entry| normalize(dispatch_entry) }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def normalize(dispatch_entry)
|
|
24
|
+
engine_name = dispatch_entry[:engine]
|
|
25
|
+
error = dispatch_entry[:error]
|
|
26
|
+
|
|
27
|
+
return error_result(engine_name, error) if error
|
|
28
|
+
|
|
29
|
+
annotate_findings(engine_name, dispatch_entry[:result])
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def annotate_findings(engine_name, result)
|
|
33
|
+
findings = (result['findings'] || []).map do |finding|
|
|
34
|
+
finding.merge('engine' => engine_name)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
{
|
|
38
|
+
'findings' => findings,
|
|
39
|
+
'overall_status' => result['overall_status'] || 'pass',
|
|
40
|
+
'summary' => result['summary'] || ''
|
|
41
|
+
}
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def error_result(engine_name, error_message)
|
|
45
|
+
{
|
|
46
|
+
'findings' => [{
|
|
47
|
+
'category' => 'other', 'severity' => 'high',
|
|
48
|
+
'file' => 'N/A', 'rule_id' => nil,
|
|
49
|
+
'summary' => "Engine #{engine_name} failed",
|
|
50
|
+
'explanation' => error_message,
|
|
51
|
+
'engine' => engine_name
|
|
52
|
+
}],
|
|
53
|
+
'overall_status' => 'fail',
|
|
54
|
+
'summary' => "Engine #{engine_name} failed: #{error_message}"
|
|
55
|
+
}
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-only
|
|
4
|
+
# Copyright (C) 2026 Hugo Antonio Sepulveda Manriquez / NeatNerds
|
|
5
|
+
|
|
6
|
+
module RosettAi
|
|
7
|
+
module Quorum
|
|
8
|
+
# Aggregates findings from multiple engines.
|
|
9
|
+
#
|
|
10
|
+
# Cross-engine findings (same category + file + rule_id from 2+ engines)
|
|
11
|
+
# are promoted by severity. Unique findings are annotated with their
|
|
12
|
+
# source engine for transparency.
|
|
13
|
+
class Comparator
|
|
14
|
+
FAIL_SEVERITIES = ['high', 'critical'].freeze
|
|
15
|
+
SEVERITY_RANK = { 'low' => 0, 'medium' => 1, 'high' => 2, 'critical' => 3 }.freeze
|
|
16
|
+
|
|
17
|
+
# Compare and aggregate collected engine results.
|
|
18
|
+
#
|
|
19
|
+
# @param collected [Hash<String, Hash>] engine_name => normalized result
|
|
20
|
+
# @return [Hash] merged result with findings, overall_status, summary
|
|
21
|
+
def compare(collected)
|
|
22
|
+
all_findings = collected.values.flat_map { |r| r['findings'] }
|
|
23
|
+
grouped = group_findings(all_findings)
|
|
24
|
+
merged = grouped.map { |_key, group| merge_group(group) }
|
|
25
|
+
|
|
26
|
+
{
|
|
27
|
+
'findings' => merged,
|
|
28
|
+
'overall_status' => determine_status(merged),
|
|
29
|
+
'summary' => build_summary(merged, collected.keys)
|
|
30
|
+
}
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def group_findings(findings)
|
|
36
|
+
findings.group_by { |f| finding_key(f) }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def finding_key(finding)
|
|
40
|
+
[finding['category'], finding['file'], finding['rule_id']].join('|')
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def merge_group(group)
|
|
44
|
+
engines = extract_engines(group)
|
|
45
|
+
merged = highest_severity_finding(group)
|
|
46
|
+
merged['engines'] = engines
|
|
47
|
+
merged['cross_engine'] = engines.size > 1
|
|
48
|
+
merged
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def extract_engines(group)
|
|
52
|
+
group.map { |entry| entry['engine'] }.uniq
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def highest_severity_finding(group)
|
|
56
|
+
group.max_by { |entry| SEVERITY_RANK.fetch(entry['severity'], -1) }.dup
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def determine_status(findings)
|
|
60
|
+
if findings.any? { |f| FAIL_SEVERITIES.include?(f['severity']) }
|
|
61
|
+
'fail'
|
|
62
|
+
elsif findings.any?
|
|
63
|
+
'warn'
|
|
64
|
+
else
|
|
65
|
+
'pass'
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def build_summary(findings, engine_names)
|
|
70
|
+
return "No issues found across #{engine_names.size} engine(s)" if findings.empty?
|
|
71
|
+
|
|
72
|
+
cross = findings.count { |f| f['cross_engine'] }
|
|
73
|
+
unique = findings.size - cross
|
|
74
|
+
parts = ["#{findings.size} finding(s) across #{engine_names.size} engine(s)"]
|
|
75
|
+
parts << "#{cross} cross-engine" if cross.positive?
|
|
76
|
+
parts << "#{unique} unique" if unique.positive?
|
|
77
|
+
parts.join(', ')
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
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 Quorum
|
|
8
|
+
# Dispatches a prompt to multiple engine executors in parallel.
|
|
9
|
+
#
|
|
10
|
+
# Uses Thread (not Ractor) because the anthropic gem and Net::HTTP
|
|
11
|
+
# are not Ractor-safe. Thread is sufficient for I/O-bound API calls.
|
|
12
|
+
class Dispatcher
|
|
13
|
+
# Dispatch a prompt to the given executors.
|
|
14
|
+
#
|
|
15
|
+
# @param prompt [String] the analysis prompt
|
|
16
|
+
# @param executors [Hash<String, Object>] engine_name => executor pairs
|
|
17
|
+
# @return [Hash<String, Hash>] engine_name => {result:, error:, elapsed:}
|
|
18
|
+
def dispatch(prompt, executors)
|
|
19
|
+
threads = spawn_threads(prompt, executors)
|
|
20
|
+
collect_thread_results(threads)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def spawn_threads(prompt, executors)
|
|
26
|
+
executors.map do |engine_name, executor|
|
|
27
|
+
Thread.new(engine_name, executor) do |name, exec|
|
|
28
|
+
Thread.current[:engine] = name
|
|
29
|
+
dispatch_single(name, exec, prompt)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def dispatch_single(engine_name, executor, prompt)
|
|
35
|
+
start = monotonic_now
|
|
36
|
+
result = executor.analyze(prompt)
|
|
37
|
+
|
|
38
|
+
{ engine: engine_name, result: result, error: nil, elapsed: monotonic_now - start }
|
|
39
|
+
rescue StandardError => e
|
|
40
|
+
{ engine: engine_name, result: nil, error: e.message, elapsed: monotonic_now - start }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def monotonic_now
|
|
44
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def collect_thread_results(threads)
|
|
48
|
+
results = {}
|
|
49
|
+
threads.each do |thread|
|
|
50
|
+
thread.join
|
|
51
|
+
results[thread[:engine]] = thread.value
|
|
52
|
+
end
|
|
53
|
+
results
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
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 Quorum
|
|
8
|
+
module Strategies
|
|
9
|
+
# Orchestrates multi-engine adopt analysis:
|
|
10
|
+
# 1. Detect available engines with adopt_api capability
|
|
11
|
+
# 2. Resolve executors via ExecutorResolver
|
|
12
|
+
# 3. Dispatch prompt to all executors in parallel
|
|
13
|
+
# 4. Collect and compare results
|
|
14
|
+
class Adopt
|
|
15
|
+
def initialize(api_key: nil)
|
|
16
|
+
@api_key = api_key
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Run quorum adopt analysis.
|
|
20
|
+
#
|
|
21
|
+
# @param prompt [String] the analysis prompt
|
|
22
|
+
# @return [Hash] aggregated result with findings, overall_status, summary
|
|
23
|
+
def run(prompt)
|
|
24
|
+
executors = resolve_executors
|
|
25
|
+
raise RosettAi::AdoptError, 'No engines with adopt_api capability detected' if executors.empty?
|
|
26
|
+
|
|
27
|
+
dispatched = Dispatcher.new.dispatch(prompt, executors)
|
|
28
|
+
collected = Collector.new.collect(dispatched)
|
|
29
|
+
Comparator.new.compare(collected)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# List engines eligible for quorum adopt.
|
|
33
|
+
#
|
|
34
|
+
# @return [Array<String>] engine names
|
|
35
|
+
def eligible_engines
|
|
36
|
+
RosettAi::Engines::Registry.available.select do |name|
|
|
37
|
+
manifest = RosettAi::Engines::Registry.manifest(name)
|
|
38
|
+
manifest.dig('capabilities', 'adopt_api') == true &&
|
|
39
|
+
manifest.dig('components', 'executor') == true
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def resolve_executors
|
|
46
|
+
eligible_engines.each_with_object({}) do |name, hash|
|
|
47
|
+
resolver = RosettAi::Adopter::ExecutorResolver.new(name)
|
|
48
|
+
hash[name] = resolver.resolve(api_key: @api_key)
|
|
49
|
+
rescue RosettAi::AdoptError => e
|
|
50
|
+
RosettAi.logger.warn("Skipping engine #{name} for quorum: #{e.message}")
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,107 @@
|
|
|
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
|
+
# Loads and validates Rosett-AI runtime configuration from ~/.config/rosett-ai/config.yml.
|
|
10
|
+
#
|
|
11
|
+
# Returns compiled defaults when no config file exists. User values are
|
|
12
|
+
# deep-merged over defaults and validated against the rai config JSON Schema.
|
|
13
|
+
class RaiConfig
|
|
14
|
+
DEFAULTS = {
|
|
15
|
+
'default_engine' => 'claude',
|
|
16
|
+
'cache' => { 'enabled' => true, 'ttl_hours' => 24 },
|
|
17
|
+
'detect' => { 'on_init' => true },
|
|
18
|
+
'dbus' => { 'allow_plugin_management' => false }
|
|
19
|
+
}.freeze
|
|
20
|
+
|
|
21
|
+
attr_reader :config_path
|
|
22
|
+
|
|
23
|
+
def initialize(config_path: nil)
|
|
24
|
+
@config_path = config_path || RosettAi.paths.rai_config_dir.join('config.yml')
|
|
25
|
+
@data = load_config
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def default_engine = @data['default_engine']
|
|
29
|
+
def cache_enabled? = @data.dig('cache', 'enabled')
|
|
30
|
+
def cache_ttl_hours = @data.dig('cache', 'ttl_hours')
|
|
31
|
+
def detect_on_init? = @data.dig('detect', 'on_init')
|
|
32
|
+
def dbus_allow_plugin_management? = @data.dig('dbus', 'allow_plugin_management') == true
|
|
33
|
+
|
|
34
|
+
def self.load(config_path: nil)
|
|
35
|
+
new(config_path: config_path)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Update a configuration key and persist to disk.
|
|
39
|
+
#
|
|
40
|
+
# @param key [String] dot-separated key (e.g. 'default_engine', 'cache.enabled')
|
|
41
|
+
# @param value [String] new value (coerced to boolean/integer where appropriate)
|
|
42
|
+
def update(key, value)
|
|
43
|
+
coerced = coerce_value(value)
|
|
44
|
+
set_nested_key(@data, key, coerced)
|
|
45
|
+
validate!(@data)
|
|
46
|
+
persist!
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def load_config
|
|
52
|
+
return DEFAULTS.dup unless @config_path.exist?
|
|
53
|
+
|
|
54
|
+
user_data = RosettAi::YamlLoader.load_file(@config_path.to_s)
|
|
55
|
+
merged = deep_merge(DEFAULTS, user_data || {})
|
|
56
|
+
validate!(merged)
|
|
57
|
+
merged
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def validate!(data)
|
|
61
|
+
schema_path = RosettAi.root.join('conf', 'schemas', 'rai_config_schema.json')
|
|
62
|
+
schema = JSON.parse(schema_path.read)
|
|
63
|
+
schemer = JSONSchemer.schema(schema)
|
|
64
|
+
errors = schemer.validate(data).to_a
|
|
65
|
+
|
|
66
|
+
return if errors.empty?
|
|
67
|
+
|
|
68
|
+
messages = errors.map { |e| "#{e['data_pointer']}: #{e['type']}" }
|
|
69
|
+
raise RosettAi::ConfigurationError,
|
|
70
|
+
"Invalid rai config at #{@config_path}: #{messages.join(', ')}"
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def persist!
|
|
74
|
+
FileUtils.mkdir_p(@config_path.dirname)
|
|
75
|
+
File.write(@config_path, YAML.dump(@data))
|
|
76
|
+
File.chmod(0o600, @config_path)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def coerce_value(value)
|
|
80
|
+
case value
|
|
81
|
+
when 'true' then true
|
|
82
|
+
when 'false' then false
|
|
83
|
+
when /\A\d+\z/ then value.to_i
|
|
84
|
+
else value
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def set_nested_key(data, key, value)
|
|
89
|
+
parts = key.split('.')
|
|
90
|
+
target = parts[0..-2].reduce(data) { |hash, k| hash[k] ||= {} }
|
|
91
|
+
target[parts.last] = value
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def deep_merge(base, overlay)
|
|
95
|
+
base.each_with_object(overlay.dup) do |(key, base_val), result|
|
|
96
|
+
overlay_val = result[key]
|
|
97
|
+
result[key] = if result.key?(key) && base_val.is_a?(Hash) && overlay_val.is_a?(Hash)
|
|
98
|
+
deep_merge(base_val, overlay_val)
|
|
99
|
+
elsif result.key?(key)
|
|
100
|
+
overlay_val
|
|
101
|
+
else
|
|
102
|
+
base_val.is_a?(Hash) ? base_val.dup : base_val
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
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 Retrofit
|
|
8
|
+
# Abstract base class for engine-specific retrofit parsers.
|
|
9
|
+
#
|
|
10
|
+
# Each engine provides a parser that knows how to discover and read
|
|
11
|
+
# native config files, convert them to Rosett-AI YAML structure, and
|
|
12
|
+
# identify unknown keys.
|
|
13
|
+
#
|
|
14
|
+
# @abstract Subclasses must implement {#discover}, {#parse}, and {#engine_name}.
|
|
15
|
+
# @author hugo
|
|
16
|
+
# @author claude
|
|
17
|
+
class BaseParser
|
|
18
|
+
# @return [String] engine identifier
|
|
19
|
+
def engine_name
|
|
20
|
+
raise NotImplementedError, "#{self.class}#engine_name must be implemented"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Discovers native config files for this engine.
|
|
24
|
+
#
|
|
25
|
+
# @return [Array<Pathname>] list of discovered config file paths
|
|
26
|
+
def discover
|
|
27
|
+
raise NotImplementedError, "#{self.class}#discover must be implemented"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Parses a native config file into Rosett-AI YAML structure.
|
|
31
|
+
#
|
|
32
|
+
# @param path [Pathname] path to the native config file
|
|
33
|
+
# @return [Hash] parsed configuration data with :data, :unknown_keys, :source_file keys
|
|
34
|
+
def parse(path)
|
|
35
|
+
raise NotImplementedError, "#{self.class}#parse must be implemented"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# @return [Boolean] true if this engine's native config is present
|
|
39
|
+
def available?
|
|
40
|
+
discover.any?
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
# Validates that a path is safe to read (within HOME, no symlink escape).
|
|
46
|
+
#
|
|
47
|
+
# @param path [Pathname] path to validate
|
|
48
|
+
# @return [Boolean] true if path is safe
|
|
49
|
+
def safe_path?(path)
|
|
50
|
+
home = Pathname.new(Dir.home)
|
|
51
|
+
resolved = path.realpath
|
|
52
|
+
resolved.to_s.start_with?(home.to_s)
|
|
53
|
+
rescue Errno::ENOENT
|
|
54
|
+
false
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Strips ANSI escape sequences and control characters from a string.
|
|
58
|
+
#
|
|
59
|
+
# @param text [String] input text
|
|
60
|
+
# @return [String] sanitized text
|
|
61
|
+
def sanitize(text)
|
|
62
|
+
text.to_s.gsub(/\e\[[0-9;]*[a-zA-Z]/, '').gsub(/[\x00-\x08\x0B\x0C\x0E-\x1F]/, '')
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,171 @@
|
|
|
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 'fileutils'
|
|
8
|
+
|
|
9
|
+
module RosettAi
|
|
10
|
+
module Retrofit
|
|
11
|
+
# Orchestrates reverse compilation from native AI tool configs to Rosett-AI YAML.
|
|
12
|
+
#
|
|
13
|
+
# Discovers installed engines, parses native configs, redacts secrets,
|
|
14
|
+
# and writes rosett-ai-compatible YAML source files.
|
|
15
|
+
#
|
|
16
|
+
# @author hugo
|
|
17
|
+
# @author claude
|
|
18
|
+
class Engine
|
|
19
|
+
PARSER_REGISTRY = {
|
|
20
|
+
'claude' => 'RosettAi::Retrofit::Parsers::ClaudeParser',
|
|
21
|
+
'cursor' => 'RosettAi::Retrofit::Parsers::CursorParser',
|
|
22
|
+
'agents_md' => 'RosettAi::Retrofit::Parsers::AgentsMdParser'
|
|
23
|
+
}.freeze
|
|
24
|
+
|
|
25
|
+
# @param output_dir [Pathname] directory to write generated YAML
|
|
26
|
+
# @param engines [Array<String>, nil] specific engines or nil for auto-detect
|
|
27
|
+
# @param force [Boolean] overwrite existing files
|
|
28
|
+
def initialize(output_dir:, engines: nil, force: false)
|
|
29
|
+
@output_dir = output_dir
|
|
30
|
+
@requested_engines = engines
|
|
31
|
+
@force = force
|
|
32
|
+
@warnings = []
|
|
33
|
+
@secret_detector = SecretDetector.new(warnings: @warnings)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Performs the retrofit operation.
|
|
37
|
+
#
|
|
38
|
+
# @return [Hash] result with :generated, :skipped, :errors, :warnings keys
|
|
39
|
+
def retrofit
|
|
40
|
+
results = { generated: [], skipped: [], errors: [], warnings: [] }
|
|
41
|
+
parsers = resolve_parsers(results)
|
|
42
|
+
|
|
43
|
+
parsers.each { |parser| retrofit_engine(parser, results) }
|
|
44
|
+
|
|
45
|
+
write_manifest(results) unless results[:generated].empty?
|
|
46
|
+
results[:warnings] = @warnings
|
|
47
|
+
results
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Simulates retrofit without writing files.
|
|
51
|
+
#
|
|
52
|
+
# @return [Hash] same structure as retrofit but read-only
|
|
53
|
+
def simulate
|
|
54
|
+
results = { generated: [], skipped: [], errors: [], warnings: [] }
|
|
55
|
+
parsers = resolve_parsers(results)
|
|
56
|
+
|
|
57
|
+
parsers.each { |parser| simulate_engine(parser, results) }
|
|
58
|
+
|
|
59
|
+
results[:warnings] = @warnings
|
|
60
|
+
results
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# @return [Array<String>] list of available engine names
|
|
64
|
+
def self.available_engines
|
|
65
|
+
PARSER_REGISTRY.keys
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
def resolve_parsers(results)
|
|
71
|
+
if @requested_engines
|
|
72
|
+
@requested_engines.filter_map do |name|
|
|
73
|
+
klass_name = PARSER_REGISTRY[name]
|
|
74
|
+
unless klass_name
|
|
75
|
+
results[:errors] << "No retrofit adapter for engine '#{name}'"
|
|
76
|
+
next
|
|
77
|
+
end
|
|
78
|
+
Object.const_get(klass_name).new
|
|
79
|
+
end
|
|
80
|
+
else
|
|
81
|
+
auto_detect_parsers
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def auto_detect_parsers
|
|
86
|
+
PARSER_REGISTRY.filter_map do |_name, klass_name|
|
|
87
|
+
parser = Object.const_get(klass_name).new
|
|
88
|
+
parser if parser.available?
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def retrofit_engine(parser, results)
|
|
93
|
+
parser.discover.each do |path|
|
|
94
|
+
parsed = parser.parse(path)
|
|
95
|
+
redacted = @secret_detector.redact(parsed[:data])
|
|
96
|
+
output_path = resolve_output_path(parser.engine_name, parsed[:scope])
|
|
97
|
+
|
|
98
|
+
write_yaml(output_path, redacted, parsed, results)
|
|
99
|
+
rescue RosettAi::RetrofitError => e
|
|
100
|
+
results[:errors] << "#{parser.engine_name}: #{e.message}"
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def simulate_engine(parser, results)
|
|
105
|
+
parser.discover.each do |path|
|
|
106
|
+
parsed = parser.parse(path)
|
|
107
|
+
@secret_detector.redact(parsed[:data])
|
|
108
|
+
output_path = resolve_output_path(parser.engine_name, parsed[:scope])
|
|
109
|
+
|
|
110
|
+
if output_path.exist? && !@force
|
|
111
|
+
results[:skipped] << output_path.to_s
|
|
112
|
+
else
|
|
113
|
+
results[:generated] << output_path.to_s
|
|
114
|
+
end
|
|
115
|
+
rescue RosettAi::RetrofitError => e
|
|
116
|
+
results[:errors] << "#{parser.engine_name}: #{e.message}"
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def resolve_output_path(engine_name, scope)
|
|
121
|
+
@output_dir.join('conf', engine_name, "#{scope}.yml")
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def write_yaml(path, data, parsed, results)
|
|
125
|
+
if path.exist? && !@force
|
|
126
|
+
results[:skipped] << path.to_s
|
|
127
|
+
return
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
path.dirname.mkpath
|
|
131
|
+
content = build_yaml_content(data, parsed)
|
|
132
|
+
File.write(path, content)
|
|
133
|
+
results[:generated] << path.to_s
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def build_yaml_content(data, parsed)
|
|
137
|
+
lines = []
|
|
138
|
+
lines << "# Retrofitted from: #{parsed[:source_file]}"
|
|
139
|
+
lines << "# Engine: #{parsed.fetch(:scope, 'unknown')}"
|
|
140
|
+
lines << "# Date: #{Time.now.utc.iso8601}"
|
|
141
|
+
lines << "# rosett-ai version: #{RosettAi::VERSION}"
|
|
142
|
+
|
|
143
|
+
unknown = parsed[:unknown_keys]
|
|
144
|
+
unless unknown.empty?
|
|
145
|
+
lines << '#'
|
|
146
|
+
lines << '# Unknown keys (not in rosett-ai schema, preserved for compatibility):'
|
|
147
|
+
unknown.each { |key| lines << "# - #{key}" }
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
lines << '---'
|
|
151
|
+
lines << YAML.dump(data).delete_prefix("---\n")
|
|
152
|
+
lines.join("\n")
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def write_manifest(results)
|
|
156
|
+
manifest_path = @output_dir.join('conf', 'retrofit_manifest.yml')
|
|
157
|
+
manifest_path.dirname.mkpath
|
|
158
|
+
|
|
159
|
+
manifest = {
|
|
160
|
+
'retrofitted_at' => Time.now.utc.iso8601,
|
|
161
|
+
'rai_version' => RosettAi::VERSION,
|
|
162
|
+
'sources' => results[:generated].map do |path|
|
|
163
|
+
{ 'output' => path, 'engine' => File.basename(File.dirname(path)) }
|
|
164
|
+
end
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
File.write(manifest_path, YAML.dump(manifest))
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|