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,103 @@
|
|
|
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 'dbus'
|
|
7
|
+
|
|
8
|
+
module RosettAi
|
|
9
|
+
module DBus
|
|
10
|
+
# D-Bus interface for focus monitoring.
|
|
11
|
+
#
|
|
12
|
+
# Interface: be.neatnerds.rosettai.FocusMonitor
|
|
13
|
+
# Methods:
|
|
14
|
+
# GetCurrentFocus() -> (ss) Return (app_id, window_title)
|
|
15
|
+
# Signals:
|
|
16
|
+
# FocusChanged(app_id: s, title: s) Emitted on active window change
|
|
17
|
+
class FocusMonitorInterface < ::DBus::Object
|
|
18
|
+
INTERFACE = 'be.neatnerds.rosettai.FocusMonitor'
|
|
19
|
+
|
|
20
|
+
dbus_interface INTERFACE do
|
|
21
|
+
dbus_method :GetCurrentFocus, 'out app_id:s, out title:s' do
|
|
22
|
+
get_current_focus
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
dbus_signal :FocusChanged, 'app_id:s, title:s'
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def initialize(path)
|
|
29
|
+
super
|
|
30
|
+
@current_app_id = ''
|
|
31
|
+
@current_title = ''
|
|
32
|
+
@adapter = nil
|
|
33
|
+
@monitor_thread = nil
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Start focus monitoring
|
|
37
|
+
def start
|
|
38
|
+
compositor = CompositorDetector.detect
|
|
39
|
+
RosettAi.logger.info("Detected compositor: #{compositor}")
|
|
40
|
+
|
|
41
|
+
@adapter = create_adapter(compositor)
|
|
42
|
+
return unless @adapter
|
|
43
|
+
|
|
44
|
+
@adapter.on_focus_change do |app_id, title|
|
|
45
|
+
handle_focus_change(app_id, title)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
@adapter.start
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Stop focus monitoring
|
|
52
|
+
def stop
|
|
53
|
+
@adapter&.stop
|
|
54
|
+
@adapter = nil
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
# Get current focus
|
|
60
|
+
#
|
|
61
|
+
# @return [Array<String, String>] [app_id, title]
|
|
62
|
+
def get_current_focus
|
|
63
|
+
[@current_app_id, @current_title]
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Handle focus change event
|
|
67
|
+
#
|
|
68
|
+
# @param app_id [String] Application identifier
|
|
69
|
+
# @param title [String] Window title
|
|
70
|
+
def handle_focus_change(app_id, title)
|
|
71
|
+
return if app_id == @current_app_id && title == @current_title
|
|
72
|
+
|
|
73
|
+
@current_app_id = app_id
|
|
74
|
+
@current_title = title
|
|
75
|
+
|
|
76
|
+
RosettAi.logger.debug("Focus changed: #{app_id} - #{title}")
|
|
77
|
+
FocusChanged(app_id, title)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Create appropriate adapter for the compositor
|
|
81
|
+
#
|
|
82
|
+
# @param compositor [Symbol] Compositor type
|
|
83
|
+
# @return [FocusAdapters::Base, nil]
|
|
84
|
+
def create_adapter(compositor)
|
|
85
|
+
case compositor
|
|
86
|
+
when :sway, :i3
|
|
87
|
+
FocusAdapters::I3Adapter.new
|
|
88
|
+
when :hyprland
|
|
89
|
+
FocusAdapters::HyprlandAdapter.new
|
|
90
|
+
when :gnome
|
|
91
|
+
FocusAdapters::GnomeAdapter.new
|
|
92
|
+
when :kwin
|
|
93
|
+
FocusAdapters::KwinAdapter.new
|
|
94
|
+
when :x11
|
|
95
|
+
FocusAdapters::X11Adapter.new
|
|
96
|
+
else
|
|
97
|
+
RosettAi.logger.warn("No focus adapter for compositor: #{compositor}")
|
|
98
|
+
nil
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
@@ -0,0 +1,213 @@
|
|
|
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 'dbus'
|
|
7
|
+
|
|
8
|
+
module RosettAi
|
|
9
|
+
module DBus
|
|
10
|
+
# D-Bus interface for Rosett-AI management operations.
|
|
11
|
+
#
|
|
12
|
+
# Interface: be.neatnerds.rosettai.Manager
|
|
13
|
+
# Methods:
|
|
14
|
+
# Compile(engine: s) -> s Run compilation for specified engine
|
|
15
|
+
# SwitchContext(name: s) Switch to named context
|
|
16
|
+
# GetStatus() -> a{sv} Return service status dictionary
|
|
17
|
+
# Properties:
|
|
18
|
+
# Version: s (read) Service version string
|
|
19
|
+
# Signals:
|
|
20
|
+
# ContextChanged(name: s) Emitted after context switch
|
|
21
|
+
#
|
|
22
|
+
# Security:
|
|
23
|
+
# - Rate limiting (10 req/s, burst 20) prevents DoS via rapid method calls
|
|
24
|
+
# - Compile operations have stricter limits (2 req/s, burst 5)
|
|
25
|
+
class ManagerInterface < ::DBus::Object
|
|
26
|
+
INTERFACE = 'be.neatnerds.rosettai.Manager'
|
|
27
|
+
|
|
28
|
+
# Rate limit configurations per operation type
|
|
29
|
+
RATE_LIMITS = {
|
|
30
|
+
default: { rate: 10, burst: 20 },
|
|
31
|
+
compile: { rate: 2, burst: 5 }, # Compile is expensive
|
|
32
|
+
config: { rate: 5, burst: 10 } # Config changes need moderation
|
|
33
|
+
}.freeze
|
|
34
|
+
|
|
35
|
+
# Keys allowed via D-Bus SetConfig — prevents arbitrary config mutation
|
|
36
|
+
SETCONFIG_ALLOWED_KEYS = [
|
|
37
|
+
'default_engine',
|
|
38
|
+
'cache.enabled',
|
|
39
|
+
'cache.ttl_hours',
|
|
40
|
+
'detect.on_init'
|
|
41
|
+
].freeze
|
|
42
|
+
|
|
43
|
+
dbus_interface INTERFACE do
|
|
44
|
+
dbus_method :Compile, 'in engine:s, out result:s' do |engine|
|
|
45
|
+
compile(engine)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
dbus_method :SwitchContext, 'in context_name:s' do |context_name|
|
|
49
|
+
switch_context(context_name)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
dbus_method :GetStatus, 'out status:a{sv}' do
|
|
53
|
+
get_status
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
dbus_method :Shutdown, '' do
|
|
57
|
+
shutdown
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
dbus_method :SetConfig, 'in key:s, in value:s, out result:s' do |key, value|
|
|
61
|
+
set_config(key, value)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
dbus_method :ListEngines, 'out engines:a(ss)' do
|
|
65
|
+
list_engines
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
dbus_attr_reader :version, 's'
|
|
69
|
+
|
|
70
|
+
dbus_signal :ContextChanged, 'context_name:s'
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
attr_writer :service
|
|
74
|
+
|
|
75
|
+
def initialize(path)
|
|
76
|
+
super
|
|
77
|
+
@current_context = 'default'
|
|
78
|
+
@service = nil
|
|
79
|
+
@rate_limiters = build_rate_limiters
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Version property
|
|
83
|
+
#
|
|
84
|
+
# @return [String]
|
|
85
|
+
def version
|
|
86
|
+
RosettAi::VERSION
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
private
|
|
90
|
+
|
|
91
|
+
# Run compilation for the specified engine
|
|
92
|
+
#
|
|
93
|
+
# @param engine [String] Engine name (claude, generic, agents_md, etc.)
|
|
94
|
+
# @return [String] Compilation result message
|
|
95
|
+
def compile(engine)
|
|
96
|
+
rate_limit!(:compile, 'Compile')
|
|
97
|
+
RosettAi.logger.info("D-Bus compile requested for engine: #{engine}")
|
|
98
|
+
|
|
99
|
+
# Use the compilation pipeline
|
|
100
|
+
backend = RosettAi::Compiler::Backend.for(engine)
|
|
101
|
+
pipeline = RosettAi::Compiler::CompilationPipeline.new(
|
|
102
|
+
source_dir: RosettAi.root.join('conf'),
|
|
103
|
+
target_dir: resolve_target_dir(backend),
|
|
104
|
+
schema_dir: RosettAi.root.join('conf', 'schemas'),
|
|
105
|
+
backend: backend
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
results = pipeline.compile
|
|
109
|
+
count = results.size
|
|
110
|
+
"Compiled #{count} file(s) successfully"
|
|
111
|
+
rescue RosettAi::CompileError => e
|
|
112
|
+
"Compilation failed: #{e.message}"
|
|
113
|
+
rescue StandardError => e
|
|
114
|
+
RosettAi.logger.error("Compile error: #{e.message}")
|
|
115
|
+
"Error: #{e.message}"
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Switch to named context
|
|
119
|
+
#
|
|
120
|
+
# @param context_name [String] Context name to switch to
|
|
121
|
+
def switch_context(context_name)
|
|
122
|
+
rate_limit!(:default, 'SwitchContext')
|
|
123
|
+
RosettAi.logger.info("D-Bus context switch: #{@current_context} -> #{context_name}")
|
|
124
|
+
@current_context = context_name
|
|
125
|
+
|
|
126
|
+
# Emit signal
|
|
127
|
+
ContextChanged(context_name)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Get service status
|
|
131
|
+
#
|
|
132
|
+
# @return [Hash] Status dictionary
|
|
133
|
+
def get_status
|
|
134
|
+
rate_limit!(:default, 'GetStatus')
|
|
135
|
+
{
|
|
136
|
+
'version' => RosettAi::VERSION,
|
|
137
|
+
'running' => true,
|
|
138
|
+
'current_context' => @current_context,
|
|
139
|
+
'pid' => Process.pid,
|
|
140
|
+
'dbus_name' => Service::BUS_NAME
|
|
141
|
+
}
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Initiate graceful service shutdown.
|
|
145
|
+
# Thread is required to avoid blocking the D-Bus event loop during shutdown.
|
|
146
|
+
def shutdown
|
|
147
|
+
RosettAi.logger.info('D-Bus shutdown requested')
|
|
148
|
+
Thread.new do
|
|
149
|
+
sleep(0.1)
|
|
150
|
+
@service&.stop
|
|
151
|
+
end
|
|
152
|
+
nil
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Update a configuration key via RaiConfig
|
|
156
|
+
#
|
|
157
|
+
# @param key [String] dot-separated config key (e.g. 'default_engine')
|
|
158
|
+
# @param value [String] new value
|
|
159
|
+
# @return [String] result message
|
|
160
|
+
def set_config(key, value)
|
|
161
|
+
rate_limit!(:config, 'SetConfig')
|
|
162
|
+
return reject_disallowed_key(key) unless SETCONFIG_ALLOWED_KEYS.include?(key)
|
|
163
|
+
|
|
164
|
+
RosettAi.logger.info("D-Bus SetConfig: #{key} = #{value}")
|
|
165
|
+
RosettAi.rai_config.update(key, value)
|
|
166
|
+
"OK: #{key} = #{value}"
|
|
167
|
+
rescue StandardError => e
|
|
168
|
+
RosettAi.logger.error("SetConfig error: #{e.message}")
|
|
169
|
+
"Error: #{e.message}"
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# List available engines with display names.
|
|
173
|
+
#
|
|
174
|
+
# @return [Array<Array(String, String)>] [name, display_name] tuples
|
|
175
|
+
def list_engines
|
|
176
|
+
rate_limit!(:default, 'ListEngines')
|
|
177
|
+
RosettAi::Engines::Registry.available.map do |name|
|
|
178
|
+
manifest = RosettAi::Engines::Registry.manifest(name)
|
|
179
|
+
[name, manifest['display_name'] || name]
|
|
180
|
+
end
|
|
181
|
+
rescue StandardError => e
|
|
182
|
+
RosettAi.logger.error("ListEngines error: #{e.message}")
|
|
183
|
+
[]
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def reject_disallowed_key(key)
|
|
187
|
+
RosettAi.logger.warn("D-Bus SetConfig rejected disallowed key: #{key}")
|
|
188
|
+
"Error: key '#{key}' is not allowed via D-Bus (permitted: #{SETCONFIG_ALLOWED_KEYS.join(', ')})"
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def resolve_target_dir(backend)
|
|
192
|
+
dir = backend.profile.output_dir
|
|
193
|
+
dir ? Pathname.new(File.expand_path(dir)) : RosettAi.paths.rules_dir
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def build_rate_limiters
|
|
197
|
+
RATE_LIMITS.transform_values do |config|
|
|
198
|
+
RateLimiter.new(rate: config[:rate], burst: config[:burst])
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def rate_limit!(category, method_name)
|
|
203
|
+
return unless @rate_limiters # Skip rate limiting in tests without full init
|
|
204
|
+
|
|
205
|
+
limiter = @rate_limiters[category] || @rate_limiters[:default]
|
|
206
|
+
limiter.consume!(method_name)
|
|
207
|
+
rescue RateLimiter::RateLimitExceeded => e
|
|
208
|
+
RosettAi.logger.warn("D-Bus rate limit exceeded: #{e.message}")
|
|
209
|
+
raise ::DBus::Error, "org.freedesktop.DBus.Error.LimitsExceeded: #{e.message}"
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
@@ -0,0 +1,169 @@
|
|
|
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 'dbus'
|
|
7
|
+
|
|
8
|
+
module RosettAi
|
|
9
|
+
module DBus
|
|
10
|
+
# D-Bus interface for plugin management.
|
|
11
|
+
#
|
|
12
|
+
# Interface: be.neatnerds.rosettai.PluginManager
|
|
13
|
+
# Methods:
|
|
14
|
+
# ListPlugins(type: s) -> a(sss) Return array of [name, display_name, version]
|
|
15
|
+
# InstallPlugin(type: s, name: s) -> (bs) Install a plugin
|
|
16
|
+
# RemovePlugin(type: s, name: s) -> (bs) Remove a plugin
|
|
17
|
+
# Signals:
|
|
18
|
+
# PluginInstalled(type: s, name: s, version: s)
|
|
19
|
+
# PluginRemoved(type: s, name: s)
|
|
20
|
+
#
|
|
21
|
+
# @author hugo
|
|
22
|
+
# @author claude
|
|
23
|
+
class PluginManagerInterface < ::DBus::Object
|
|
24
|
+
INTERFACE = 'be.neatnerds.rosettai.PluginManager'
|
|
25
|
+
|
|
26
|
+
VALID_TYPES = ['engine', 'gui', 'mcp'].freeze
|
|
27
|
+
|
|
28
|
+
dbus_interface INTERFACE do
|
|
29
|
+
dbus_method :ListPlugins, 'in type:s, out plugins:aas' do |type|
|
|
30
|
+
list_plugins(type)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
dbus_method :InstallPlugin, 'in type:s, in name:s, out success:b, out message:s' do |type, name|
|
|
34
|
+
install_plugin(type, name)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
dbus_method :RemovePlugin, 'in type:s, in name:s, out success:b, out message:s' do |type, name|
|
|
38
|
+
remove_plugin(type, name)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
dbus_signal :PluginInstalled, 'type:s, name:s, version:s'
|
|
42
|
+
dbus_signal :PluginRemoved, 'type:s, name:s'
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
# List plugins of a given type from the registry.
|
|
48
|
+
#
|
|
49
|
+
# @param type [String] plugin type ('engine', 'gui', 'mcp')
|
|
50
|
+
# @return [Array<Array<String>>] array of [name, display_name, version] tuples
|
|
51
|
+
def list_plugins(type)
|
|
52
|
+
validate_type!(type)
|
|
53
|
+
type_sym = type.to_sym
|
|
54
|
+
plugins = RosettAi::Plugins::Registry.available(type_sym)
|
|
55
|
+
|
|
56
|
+
[plugins.map do |name|
|
|
57
|
+
mod = RosettAi::Plugins::Registry.plugin_module(type_sym, name)
|
|
58
|
+
[name, mod.display_name, mod.version]
|
|
59
|
+
end]
|
|
60
|
+
rescue StandardError => e
|
|
61
|
+
RosettAi.logger.error("ListPlugins failed: #{e.message}")
|
|
62
|
+
[[]]
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Install a plugin using the appropriate package manager.
|
|
66
|
+
#
|
|
67
|
+
# @param type [String] plugin type
|
|
68
|
+
# @param name [String] plugin name
|
|
69
|
+
# @return [Array(Boolean, String)] [success, message]
|
|
70
|
+
def install_plugin(type, name)
|
|
71
|
+
authorize_plugin_management!
|
|
72
|
+
validate_type!(type)
|
|
73
|
+
validate_name!(name)
|
|
74
|
+
RosettAi.logger.warn("D-Bus plugin install: #{type}/#{name}")
|
|
75
|
+
|
|
76
|
+
pm = select_package_manager
|
|
77
|
+
package_name = plugin_package_name(type, name)
|
|
78
|
+
|
|
79
|
+
if pm.install(package_name)
|
|
80
|
+
RosettAi::Plugins::Registry.reset!
|
|
81
|
+
RosettAi::Plugins::Registry.discover!
|
|
82
|
+
self.PluginInstalled(type, name, 'installed')
|
|
83
|
+
[true, "Installed #{package_name}"]
|
|
84
|
+
else
|
|
85
|
+
[false, "Failed to install #{package_name}"]
|
|
86
|
+
end
|
|
87
|
+
rescue ::DBus::Error
|
|
88
|
+
raise
|
|
89
|
+
rescue PackageManager::Base::PackageManagerError => e
|
|
90
|
+
[false, e.message]
|
|
91
|
+
rescue StandardError => e
|
|
92
|
+
RosettAi.logger.error("InstallPlugin failed: #{e.message}")
|
|
93
|
+
[false, "Error: #{e.message}"]
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Remove a plugin using the appropriate package manager.
|
|
97
|
+
#
|
|
98
|
+
# @param type [String] plugin type
|
|
99
|
+
# @param name [String] plugin name
|
|
100
|
+
# @return [Array(Boolean, String)] [success, message]
|
|
101
|
+
def remove_plugin(type, name)
|
|
102
|
+
authorize_plugin_management!
|
|
103
|
+
validate_type!(type)
|
|
104
|
+
validate_name!(name)
|
|
105
|
+
RosettAi.logger.warn("D-Bus plugin remove: #{type}/#{name}")
|
|
106
|
+
|
|
107
|
+
pm = select_package_manager
|
|
108
|
+
package_name = plugin_package_name(type, name)
|
|
109
|
+
|
|
110
|
+
if pm.remove(package_name)
|
|
111
|
+
RosettAi::Plugins::Registry.reset!
|
|
112
|
+
RosettAi::Plugins::Registry.discover!
|
|
113
|
+
self.PluginRemoved(type, name)
|
|
114
|
+
[true, "Removed #{package_name}"]
|
|
115
|
+
else
|
|
116
|
+
[false, "Failed to remove #{package_name}"]
|
|
117
|
+
end
|
|
118
|
+
rescue ::DBus::Error
|
|
119
|
+
raise
|
|
120
|
+
rescue PackageManager::Base::PackageManagerError => e
|
|
121
|
+
[false, e.message]
|
|
122
|
+
rescue StandardError => e
|
|
123
|
+
RosettAi.logger.error("RemovePlugin failed: #{e.message}")
|
|
124
|
+
[false, "Error: #{e.message}"]
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Verify that plugin management via D-Bus is explicitly enabled in config.
|
|
128
|
+
#
|
|
129
|
+
# @raise [::DBus::Error] when dbus.allow_plugin_management is not true
|
|
130
|
+
def authorize_plugin_management!
|
|
131
|
+
return if RosettAi.rai_config.dbus_allow_plugin_management?
|
|
132
|
+
|
|
133
|
+
raise ::DBus::Error,
|
|
134
|
+
'be.neatnerds.rosettai.Error.NotAuthorized: Plugin management via D-Bus is disabled. ' \
|
|
135
|
+
'Set dbus.allow_plugin_management: true in ~/.config/rosett-ai/config.yml'
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def validate_type!(type)
|
|
139
|
+
return if VALID_TYPES.include?(type)
|
|
140
|
+
|
|
141
|
+
raise ArgumentError, "Invalid plugin type: #{type}. Valid: #{VALID_TYPES.join(', ')}"
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def validate_name!(name)
|
|
145
|
+
return if name.match?(/\A[a-z0-9][a-z0-9_-]*\z/)
|
|
146
|
+
|
|
147
|
+
raise ArgumentError, "Invalid plugin name: #{name}"
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Map plugin type + name to a package name.
|
|
151
|
+
# Convention: rosett-ai-<type>-<name> for apt, rosett-ai-<type>-<name> for gems.
|
|
152
|
+
#
|
|
153
|
+
# @return [String]
|
|
154
|
+
def plugin_package_name(type, name)
|
|
155
|
+
"rosett-ai-#{type}-#{name}"
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Select the best available package manager.
|
|
159
|
+
#
|
|
160
|
+
# @return [PackageManager::Base]
|
|
161
|
+
def select_package_manager
|
|
162
|
+
apt = PackageManager::Apt.new
|
|
163
|
+
return apt if apt.available?
|
|
164
|
+
|
|
165
|
+
PackageManager::GemBackend.new
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
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 DBus
|
|
8
|
+
# Token bucket rate limiter for D-Bus method calls.
|
|
9
|
+
#
|
|
10
|
+
# Provides application-level rate limiting to prevent resource exhaustion
|
|
11
|
+
# from rapid D-Bus method invocations. Each method type can have its own
|
|
12
|
+
# rate limit.
|
|
13
|
+
#
|
|
14
|
+
# Thread-safe: uses mutex for bucket state.
|
|
15
|
+
#
|
|
16
|
+
# @example
|
|
17
|
+
# limiter = RateLimiter.new(rate: 10, burst: 20)
|
|
18
|
+
# if limiter.allow?
|
|
19
|
+
# # process request
|
|
20
|
+
# else
|
|
21
|
+
# raise RateLimitExceeded
|
|
22
|
+
# end
|
|
23
|
+
class RateLimiter
|
|
24
|
+
# Raised when rate limit is exceeded
|
|
25
|
+
class RateLimitExceeded < RosettAi::Error
|
|
26
|
+
def initialize(method_name = nil)
|
|
27
|
+
msg = method_name ? "Rate limit exceeded for #{method_name}" : 'Rate limit exceeded'
|
|
28
|
+
super(msg)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Default limits (requests per second / burst capacity)
|
|
33
|
+
DEFAULT_RATE = 10
|
|
34
|
+
DEFAULT_BURST = 20
|
|
35
|
+
|
|
36
|
+
# @param rate [Numeric] tokens added per second
|
|
37
|
+
# @param burst [Integer] maximum bucket capacity
|
|
38
|
+
def initialize(rate: DEFAULT_RATE, burst: DEFAULT_BURST)
|
|
39
|
+
@rate = rate.to_f
|
|
40
|
+
@burst = burst
|
|
41
|
+
@tokens = burst.to_f
|
|
42
|
+
@last_update = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
43
|
+
@mutex = Mutex.new
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Check if a request is allowed and consume a token if so.
|
|
47
|
+
#
|
|
48
|
+
# @return [Boolean] true if request is allowed
|
|
49
|
+
def allow?
|
|
50
|
+
@mutex.synchronize do
|
|
51
|
+
refill_tokens
|
|
52
|
+
if @tokens >= 1.0
|
|
53
|
+
@tokens -= 1.0
|
|
54
|
+
true
|
|
55
|
+
else
|
|
56
|
+
false
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Consume a token or raise RateLimitExceeded.
|
|
62
|
+
#
|
|
63
|
+
# @param method_name [String, nil] optional method name for error message
|
|
64
|
+
# @raise [RateLimitExceeded] if no tokens available
|
|
65
|
+
def consume!(method_name = nil)
|
|
66
|
+
raise RateLimitExceeded, method_name unless allow?
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Current available tokens (for monitoring/debugging).
|
|
70
|
+
#
|
|
71
|
+
# @return [Float] current token count
|
|
72
|
+
def available_tokens
|
|
73
|
+
@mutex.synchronize do
|
|
74
|
+
refill_tokens
|
|
75
|
+
@tokens
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
private
|
|
80
|
+
|
|
81
|
+
def refill_tokens
|
|
82
|
+
now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
83
|
+
elapsed = now - @last_update
|
|
84
|
+
@tokens = [(@tokens + (elapsed * @rate)), @burst.to_f].min
|
|
85
|
+
@last_update = now
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-only
|
|
4
|
+
# Copyright (C) 2026 Hugo Antonio Sepulveda Manriquez / NeatNerds
|
|
5
|
+
|
|
6
|
+
require 'dbus'
|
|
7
|
+
|
|
8
|
+
module RosettAi
|
|
9
|
+
module DBus
|
|
10
|
+
# Main D-Bus session service for Rosett-AI.
|
|
11
|
+
#
|
|
12
|
+
# Exports interfaces under be.neatnerds.rosettai bus name:
|
|
13
|
+
# - be.neatnerds.rosettai.Manager: compile, context switching, status
|
|
14
|
+
# - be.neatnerds.rosettai.FocusMonitor: active window tracking
|
|
15
|
+
# - org.kde.StatusNotifierItem: system tray presence
|
|
16
|
+
#
|
|
17
|
+
# The service is designed to gracefully degrade when D-Bus is unavailable.
|
|
18
|
+
class Service
|
|
19
|
+
BUS_NAME = 'be.neatnerds.rosettai'
|
|
20
|
+
OBJECT_PATH = '/be/neatnerds/rosett-ai'
|
|
21
|
+
|
|
22
|
+
attr_reader :bus, :main_loop, :manager, :focus_monitor, :status_notifier
|
|
23
|
+
|
|
24
|
+
def initialize
|
|
25
|
+
@running = false
|
|
26
|
+
@main_loop = nil
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Start the D-Bus service
|
|
30
|
+
#
|
|
31
|
+
# @return [Boolean] true if started successfully
|
|
32
|
+
def start
|
|
33
|
+
return false if @running
|
|
34
|
+
|
|
35
|
+
connect_to_session_bus
|
|
36
|
+
export_interfaces
|
|
37
|
+
request_bus_name
|
|
38
|
+
@running = true
|
|
39
|
+
true
|
|
40
|
+
rescue ::DBus::Error => e
|
|
41
|
+
RosettAi.logger.error("D-Bus service failed to start: #{e.message}")
|
|
42
|
+
false
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Run the event loop (blocking)
|
|
46
|
+
def run
|
|
47
|
+
return unless @running
|
|
48
|
+
|
|
49
|
+
@main_loop = ::DBus::Main.new
|
|
50
|
+
@main_loop << @bus
|
|
51
|
+
RosettAi.logger.info("D-Bus service running on #{BUS_NAME}")
|
|
52
|
+
@main_loop.run
|
|
53
|
+
rescue Interrupt
|
|
54
|
+
stop
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Stop the D-Bus service
|
|
58
|
+
def stop
|
|
59
|
+
return unless @running
|
|
60
|
+
|
|
61
|
+
@main_loop&.quit
|
|
62
|
+
@focus_monitor&.stop
|
|
63
|
+
@running = false
|
|
64
|
+
RosettAi.logger.info('D-Bus service stopped')
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Check if the service is running
|
|
68
|
+
#
|
|
69
|
+
# @return [Boolean]
|
|
70
|
+
def running?
|
|
71
|
+
@running
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Check if D-Bus session bus is available
|
|
75
|
+
#
|
|
76
|
+
# @return [Boolean]
|
|
77
|
+
def self.dbus_available?
|
|
78
|
+
::DBus::SessionBus.instance
|
|
79
|
+
true
|
|
80
|
+
rescue ::DBus::Error
|
|
81
|
+
false
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
private
|
|
85
|
+
|
|
86
|
+
def connect_to_session_bus
|
|
87
|
+
@bus = ::DBus::SessionBus.instance
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def export_interfaces
|
|
91
|
+
@service = @bus.request_service(BUS_NAME)
|
|
92
|
+
|
|
93
|
+
@manager = ManagerInterface.new(OBJECT_PATH)
|
|
94
|
+
@manager.service = self
|
|
95
|
+
@focus_monitor = FocusMonitorInterface.new(OBJECT_PATH)
|
|
96
|
+
|
|
97
|
+
@service.export(@manager)
|
|
98
|
+
@service.export(@focus_monitor)
|
|
99
|
+
|
|
100
|
+
export_status_notifier
|
|
101
|
+
|
|
102
|
+
# Start focus monitoring
|
|
103
|
+
@focus_monitor.start
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def export_status_notifier
|
|
107
|
+
@status_notifier = StatusNotifierInterface.new("#{OBJECT_PATH}/StatusNotifier")
|
|
108
|
+
@service.export(@status_notifier)
|
|
109
|
+
RosettAi.logger.debug('StatusNotifierItem exported')
|
|
110
|
+
rescue StandardError => e
|
|
111
|
+
RosettAi.logger.warn("StatusNotifierItem unavailable: #{e.message}")
|
|
112
|
+
@status_notifier = nil
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def request_bus_name
|
|
116
|
+
# Bus name already requested via request_service above
|
|
117
|
+
RosettAi.logger.debug("Registered bus name: #{BUS_NAME}")
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|