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,77 @@
|
|
|
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
|
+
# Detects the current compositor/window manager.
|
|
11
|
+
#
|
|
12
|
+
# Detection order (first match wins):
|
|
13
|
+
# 1. HYPRLAND_INSTANCE_SIGNATURE env var → :hyprland
|
|
14
|
+
# 2. SWAYSOCK env var → :sway
|
|
15
|
+
# 3. I3SOCK env var → :i3
|
|
16
|
+
# 4. org.gnome.Shell D-Bus name active → :gnome
|
|
17
|
+
# 5. org.kde.KWin D-Bus name active → :kwin
|
|
18
|
+
# 6. DISPLAY set, WAYLAND_DISPLAY unset → :x11
|
|
19
|
+
# 7. else → :unknown
|
|
20
|
+
module CompositorDetector
|
|
21
|
+
COMPOSITOR_CHECKS = [
|
|
22
|
+
{ env: 'HYPRLAND_INSTANCE_SIGNATURE', type: :hyprland },
|
|
23
|
+
{ env: 'SWAYSOCK', type: :sway },
|
|
24
|
+
{ env: 'I3SOCK', type: :i3 }
|
|
25
|
+
].freeze
|
|
26
|
+
|
|
27
|
+
DBUS_NAMES = {
|
|
28
|
+
'org.gnome.Shell' => :gnome,
|
|
29
|
+
'org.kde.KWin' => :kwin
|
|
30
|
+
}.freeze
|
|
31
|
+
|
|
32
|
+
class << self
|
|
33
|
+
# Detect the current compositor
|
|
34
|
+
#
|
|
35
|
+
# @return [Symbol] Compositor type
|
|
36
|
+
def detect
|
|
37
|
+
# Check environment variables first
|
|
38
|
+
COMPOSITOR_CHECKS.each do |check|
|
|
39
|
+
return check[:type] if ENV.key?(check[:env])
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Check D-Bus names
|
|
43
|
+
compositor = detect_via_dbus
|
|
44
|
+
return compositor if compositor
|
|
45
|
+
|
|
46
|
+
# Check for X11
|
|
47
|
+
return :x11 if x11_only?
|
|
48
|
+
|
|
49
|
+
:unknown
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def detect_via_dbus
|
|
55
|
+
bus = ::DBus::SessionBus.instance
|
|
56
|
+
proxy = bus.service('org.freedesktop.DBus').object('/org/freedesktop/DBus')
|
|
57
|
+
proxy.introspect
|
|
58
|
+
proxy.default_iface = 'org.freedesktop.DBus'
|
|
59
|
+
|
|
60
|
+
names = proxy.ListNames.first
|
|
61
|
+
|
|
62
|
+
DBUS_NAMES.each do |name, type|
|
|
63
|
+
return type if names.include?(name)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
nil
|
|
67
|
+
rescue ::DBus::Error
|
|
68
|
+
nil
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def x11_only?
|
|
72
|
+
ENV.key?('DISPLAY') && !ENV.key?('WAYLAND_DISPLAY')
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
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 DBus
|
|
8
|
+
module FocusAdapters
|
|
9
|
+
# Base class for compositor-specific focus adapters.
|
|
10
|
+
#
|
|
11
|
+
# Each adapter implements the same interface:
|
|
12
|
+
# - #start: Begin monitoring focus changes
|
|
13
|
+
# - #stop: Stop monitoring
|
|
14
|
+
# - #on_focus_change(&block): Register callback for focus changes
|
|
15
|
+
#
|
|
16
|
+
# Callbacks receive (app_id, title) arguments.
|
|
17
|
+
class Base
|
|
18
|
+
def initialize
|
|
19
|
+
@callbacks = []
|
|
20
|
+
@running = false
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Register callback for focus changes
|
|
24
|
+
#
|
|
25
|
+
# @yield [app_id, title] Called on focus change
|
|
26
|
+
def on_focus_change(&block)
|
|
27
|
+
@callbacks << block
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Start monitoring focus changes
|
|
31
|
+
def start
|
|
32
|
+
raise NotImplementedError, "#{self.class}#start must be implemented"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Stop monitoring
|
|
36
|
+
def stop
|
|
37
|
+
@running = false
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Check if monitoring is active
|
|
41
|
+
#
|
|
42
|
+
# @return [Boolean]
|
|
43
|
+
def running?
|
|
44
|
+
@running
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
protected
|
|
48
|
+
|
|
49
|
+
# Notify all registered callbacks of a focus change
|
|
50
|
+
#
|
|
51
|
+
# @param app_id [String] Application identifier
|
|
52
|
+
# @param title [String] Window title
|
|
53
|
+
def notify_focus_change(app_id, title)
|
|
54
|
+
@callbacks.each { |cb| cb.call(app_id.to_s, title.to_s) }
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,172 @@
|
|
|
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
|
+
module FocusAdapters
|
|
11
|
+
# Focus adapter for GNOME Shell.
|
|
12
|
+
#
|
|
13
|
+
# Uses GNOME Shell's D-Bus interface to query the focused window.
|
|
14
|
+
# For GNOME < 41, uses org.gnome.Shell.Eval (JavaScript evaluation).
|
|
15
|
+
# For GNOME >= 41, Eval is removed; falls back to xdotool + xprop.
|
|
16
|
+
class GnomeAdapter < Base
|
|
17
|
+
POLL_INTERVAL = 0.5 # seconds
|
|
18
|
+
GNOME_EVAL_REMOVED_VERSION = 41
|
|
19
|
+
|
|
20
|
+
def initialize
|
|
21
|
+
super
|
|
22
|
+
@bus = nil
|
|
23
|
+
@thread = nil
|
|
24
|
+
@last_app_id = nil
|
|
25
|
+
@last_title = nil
|
|
26
|
+
@use_xdotool = false
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def start
|
|
30
|
+
return if @running
|
|
31
|
+
|
|
32
|
+
@bus = ::DBus::SessionBus.instance
|
|
33
|
+
configure_focus_method
|
|
34
|
+
@running = true
|
|
35
|
+
@thread = Thread.new { run_poll_loop }
|
|
36
|
+
rescue ::DBus::Error => e
|
|
37
|
+
RosettAi.logger.error("Failed to connect to D-Bus: #{e.message}")
|
|
38
|
+
@running = false
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def stop
|
|
42
|
+
super
|
|
43
|
+
@thread&.kill
|
|
44
|
+
@bus = nil
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
# Select the focus detection strategy based on GNOME version.
|
|
50
|
+
def configure_focus_method
|
|
51
|
+
version = detect_gnome_version
|
|
52
|
+
@use_xdotool = version.to_i >= GNOME_EVAL_REMOVED_VERSION
|
|
53
|
+
return unless @use_xdotool
|
|
54
|
+
|
|
55
|
+
RosettAi.logger.warn(
|
|
56
|
+
"GNOME #{version} detected; org.gnome.Shell.Eval is unavailable. " \
|
|
57
|
+
'Falling back to xdotool for focus detection.'
|
|
58
|
+
)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def run_poll_loop
|
|
62
|
+
while @running
|
|
63
|
+
poll_focused_window
|
|
64
|
+
sleep POLL_INTERVAL
|
|
65
|
+
end
|
|
66
|
+
rescue StandardError => e
|
|
67
|
+
RosettAi.logger.error("GNOME focus poll error: #{e.message}")
|
|
68
|
+
@running = false
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def poll_focused_window
|
|
72
|
+
if @use_xdotool
|
|
73
|
+
poll_focused_window_xdotool
|
|
74
|
+
else
|
|
75
|
+
poll_focused_window_eval
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def poll_focused_window_eval
|
|
80
|
+
result = eval_shell_js('global.display.focus_window ? global.display.focus_window.get_wm_class() : ""')
|
|
81
|
+
app_id = result || ''
|
|
82
|
+
|
|
83
|
+
title_result = eval_shell_js('global.display.focus_window ? global.display.focus_window.get_title() : ""')
|
|
84
|
+
title = title_result || ''
|
|
85
|
+
|
|
86
|
+
return if app_id == @last_app_id && title == @last_title
|
|
87
|
+
|
|
88
|
+
@last_app_id = app_id
|
|
89
|
+
@last_title = title
|
|
90
|
+
notify_focus_change(app_id, title)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def poll_focused_window_xdotool
|
|
94
|
+
app_id = poll_active_window_xdotool
|
|
95
|
+
title = app_id ? (poll_active_window_title_xdotool || '') : return
|
|
96
|
+
|
|
97
|
+
return if app_id == @last_app_id && title == @last_title
|
|
98
|
+
|
|
99
|
+
@last_app_id = app_id
|
|
100
|
+
@last_title = title
|
|
101
|
+
notify_focus_change(app_id, title)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Detect GNOME Shell version via the ShellVersion D-Bus property.
|
|
105
|
+
#
|
|
106
|
+
# @return [Integer] Major version number, or 0 if detection fails
|
|
107
|
+
def detect_gnome_version
|
|
108
|
+
shell_object = fetch_shell_object
|
|
109
|
+
shell_object['org.gnome.Shell']['ShellVersion'].to_s.split('.').first.to_i
|
|
110
|
+
rescue ::DBus::Error
|
|
111
|
+
0
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Query the active window WM_CLASS via xdotool and xprop.
|
|
115
|
+
#
|
|
116
|
+
# @return [String, nil] The WM_CLASS instance name, or nil
|
|
117
|
+
def poll_active_window_xdotool
|
|
118
|
+
window_id = IO.popen(['xdotool', 'getactivewindow'], err: File::NULL, &:read).strip
|
|
119
|
+
return if window_id.empty?
|
|
120
|
+
|
|
121
|
+
extract_wm_class(window_id)
|
|
122
|
+
rescue Errno::ENOENT
|
|
123
|
+
handle_missing_xdotool
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Extract WM_CLASS from xprop output for a given window ID.
|
|
127
|
+
def extract_wm_class(window_id)
|
|
128
|
+
output = IO.popen(['xprop', '-id', window_id, 'WM_CLASS'], err: File::NULL, &:read)
|
|
129
|
+
match = output.match(/WM_CLASS.*=\s*"([^"]+)"/)
|
|
130
|
+
match ? match[1] : nil
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Handle missing xdotool/xprop binary.
|
|
134
|
+
def handle_missing_xdotool
|
|
135
|
+
RosettAi.logger.warn(
|
|
136
|
+
'xdotool or xprop not found. Install xdotool for focus detection on GNOME 41+: ' \
|
|
137
|
+
'sudo apt install xdotool'
|
|
138
|
+
)
|
|
139
|
+
@use_xdotool = false
|
|
140
|
+
nil
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Query the active window title via xdotool.
|
|
144
|
+
#
|
|
145
|
+
# @return [String, nil] The window title, or nil
|
|
146
|
+
def poll_active_window_title_xdotool
|
|
147
|
+
IO.popen(['xdotool', 'getactivewindow', 'getwindowname'], err: File::NULL, &:read).strip
|
|
148
|
+
rescue Errno::ENOENT
|
|
149
|
+
nil
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Fetch the GNOME Shell D-Bus object.
|
|
153
|
+
def fetch_shell_object
|
|
154
|
+
service = @bus.service('org.gnome.Shell')
|
|
155
|
+
object = service.object('/org/gnome/Shell')
|
|
156
|
+
object.introspect
|
|
157
|
+
object
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def eval_shell_js(code)
|
|
161
|
+
object = fetch_shell_object
|
|
162
|
+
object.default_iface = 'org.gnome.Shell'
|
|
163
|
+
|
|
164
|
+
success, result = object.Eval(code)
|
|
165
|
+
success ? result.gsub(/^"|"$/, '') : nil
|
|
166
|
+
rescue ::DBus::Error
|
|
167
|
+
nil
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
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
|
+
module FocusAdapters
|
|
9
|
+
# Focus adapter for Hyprland compositor.
|
|
10
|
+
#
|
|
11
|
+
# Uses Hyprland's IPC socket2 to receive activewindow events.
|
|
12
|
+
# Socket path: $XDG_RUNTIME_DIR/hypr/$HYPRLAND_INSTANCE_SIGNATURE/.socket2.sock
|
|
13
|
+
class HyprlandAdapter < Base
|
|
14
|
+
def initialize
|
|
15
|
+
super
|
|
16
|
+
@socket = nil
|
|
17
|
+
@thread = nil
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def start
|
|
21
|
+
return if @running
|
|
22
|
+
|
|
23
|
+
socket_path = hyprland_socket_path
|
|
24
|
+
unless socket_path && File.exist?(socket_path)
|
|
25
|
+
RosettAi.logger.error('Hyprland socket not found')
|
|
26
|
+
return
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
@socket = UNIXSocket.new(socket_path)
|
|
30
|
+
@running = true
|
|
31
|
+
@thread = Thread.new { run_event_loop }
|
|
32
|
+
rescue StandardError => e
|
|
33
|
+
RosettAi.logger.error("Failed to connect to Hyprland: #{e.message}")
|
|
34
|
+
@running = false
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def stop
|
|
38
|
+
super
|
|
39
|
+
@thread&.kill
|
|
40
|
+
@socket&.close
|
|
41
|
+
@socket = nil
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def hyprland_socket_path
|
|
47
|
+
instance_sig = ENV.fetch('HYPRLAND_INSTANCE_SIGNATURE', nil)
|
|
48
|
+
return nil unless instance_sig
|
|
49
|
+
|
|
50
|
+
runtime_dir = ENV.fetch('XDG_RUNTIME_DIR', "/run/user/#{Process.uid}")
|
|
51
|
+
File.join(runtime_dir, 'hypr', instance_sig, '.socket2.sock')
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def run_event_loop
|
|
55
|
+
while @running
|
|
56
|
+
line = @socket.gets
|
|
57
|
+
break unless line
|
|
58
|
+
|
|
59
|
+
handle_event(line.strip)
|
|
60
|
+
end
|
|
61
|
+
rescue StandardError => e
|
|
62
|
+
RosettAi.logger.error("Hyprland event loop error: #{e.message}")
|
|
63
|
+
@running = false
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def handle_event(line)
|
|
67
|
+
# Event format: activewindow>>class,title
|
|
68
|
+
return unless line.start_with?('activewindow>>')
|
|
69
|
+
|
|
70
|
+
data = line.sub('activewindow>>', '')
|
|
71
|
+
app_id, title = data.split(',', 2)
|
|
72
|
+
notify_focus_change(app_id || '', title || '')
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
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
|
+
module FocusAdapters
|
|
9
|
+
# Focus adapter for i3 and sway window managers.
|
|
10
|
+
#
|
|
11
|
+
# Uses the i3ipc gem to subscribe to window::focus events
|
|
12
|
+
# via the i3/sway IPC socket.
|
|
13
|
+
class I3Adapter < Base
|
|
14
|
+
def initialize
|
|
15
|
+
super
|
|
16
|
+
@connection = nil
|
|
17
|
+
@thread = nil
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def start
|
|
21
|
+
return if @running
|
|
22
|
+
|
|
23
|
+
require 'i3ipc'
|
|
24
|
+
@connection = I3Ipc::Connection.new
|
|
25
|
+
@running = true
|
|
26
|
+
|
|
27
|
+
@thread = Thread.new { run_event_loop }
|
|
28
|
+
rescue LoadError
|
|
29
|
+
RosettAi.logger.error('i3ipc gem not available')
|
|
30
|
+
@running = false
|
|
31
|
+
rescue StandardError => e
|
|
32
|
+
RosettAi.logger.error("Failed to connect to i3/sway IPC: #{e.message}")
|
|
33
|
+
@running = false
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def stop
|
|
37
|
+
super
|
|
38
|
+
@thread&.kill
|
|
39
|
+
@connection = nil
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def run_event_loop
|
|
45
|
+
@connection.subscribe('window') do |event|
|
|
46
|
+
handle_window_event(event) if event['change'] == 'focus'
|
|
47
|
+
end
|
|
48
|
+
rescue StandardError => e
|
|
49
|
+
RosettAi.logger.error("i3/sway event loop error: #{e.message}")
|
|
50
|
+
@running = false
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def handle_window_event(event)
|
|
54
|
+
container = event['container']
|
|
55
|
+
return unless container
|
|
56
|
+
|
|
57
|
+
app_id = container['app_id'] || container['window_properties']&.fetch('class', '') || ''
|
|
58
|
+
title = container['name'] || ''
|
|
59
|
+
|
|
60
|
+
notify_focus_change(app_id, title)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -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
|
+
module FocusAdapters
|
|
11
|
+
# Focus adapter for KDE KWin.
|
|
12
|
+
#
|
|
13
|
+
# Uses KWin's D-Bus scripting API to monitor focus changes.
|
|
14
|
+
# Listens for the activeWindowChanged signal from org.kde.KWin.
|
|
15
|
+
class KwinAdapter < Base
|
|
16
|
+
def initialize
|
|
17
|
+
super
|
|
18
|
+
@bus = nil
|
|
19
|
+
@main_loop = nil
|
|
20
|
+
@thread = nil
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def start
|
|
24
|
+
return if @running
|
|
25
|
+
|
|
26
|
+
@bus = ::DBus::SessionBus.instance
|
|
27
|
+
@running = true
|
|
28
|
+
setup_signal_handler
|
|
29
|
+
@thread = Thread.new { run_event_loop }
|
|
30
|
+
rescue ::DBus::Error => e
|
|
31
|
+
RosettAi.logger.error("Failed to connect to KWin D-Bus: #{e.message}")
|
|
32
|
+
@running = false
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def stop
|
|
36
|
+
super
|
|
37
|
+
@main_loop&.quit
|
|
38
|
+
@thread&.kill
|
|
39
|
+
@bus = nil
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def setup_signal_handler
|
|
45
|
+
service = @bus.service('org.kde.KWin')
|
|
46
|
+
object = service.object('/KWin')
|
|
47
|
+
object.introspect
|
|
48
|
+
|
|
49
|
+
object.on_signal('activeWindowChanged') do
|
|
50
|
+
poll_active_window
|
|
51
|
+
end
|
|
52
|
+
rescue ::DBus::Error
|
|
53
|
+
RosettAi.logger.warn('KWin activeWindowChanged signal not available, falling back to polling')
|
|
54
|
+
@polling_mode = true
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def run_event_loop
|
|
58
|
+
if @polling_mode
|
|
59
|
+
run_poll_loop
|
|
60
|
+
else
|
|
61
|
+
@main_loop = ::DBus::Main.new
|
|
62
|
+
@main_loop << @bus
|
|
63
|
+
@main_loop.run
|
|
64
|
+
end
|
|
65
|
+
rescue StandardError => e
|
|
66
|
+
RosettAi.logger.error("KWin event loop error: #{e.message}")
|
|
67
|
+
@running = false
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def run_poll_loop
|
|
71
|
+
while @running
|
|
72
|
+
poll_active_window
|
|
73
|
+
sleep 0.5
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def poll_active_window
|
|
78
|
+
service = @bus.service('org.kde.KWin')
|
|
79
|
+
object = service.object('/KWin')
|
|
80
|
+
object.introspect
|
|
81
|
+
object.default_iface = 'org.kde.KWin'
|
|
82
|
+
|
|
83
|
+
# KWin API varies by version - try to get active window info
|
|
84
|
+
# For now, we use a basic polling approach that may need adaptation
|
|
85
|
+
app_id = ''
|
|
86
|
+
title = ''
|
|
87
|
+
|
|
88
|
+
begin
|
|
89
|
+
# Try KDE 6 approach first
|
|
90
|
+
object.default_iface = 'org.kde.KWin.Scripting'
|
|
91
|
+
# KWin scripting is complex; for now just poll basic info
|
|
92
|
+
rescue ::DBus::Error
|
|
93
|
+
# Fall through to notify
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
notify_focus_change(app_id, title)
|
|
97
|
+
rescue ::DBus::Error
|
|
98
|
+
# KWin API varies, ignore errors
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
@@ -0,0 +1,105 @@
|
|
|
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
|
+
module FocusAdapters
|
|
9
|
+
# Focus adapter for X11 window managers.
|
|
10
|
+
#
|
|
11
|
+
# Uses xprop to monitor _NET_ACTIVE_WINDOW property changes.
|
|
12
|
+
# Falls back to polling if xprop spy mode is not available.
|
|
13
|
+
class X11Adapter < Base
|
|
14
|
+
POLL_INTERVAL = 0.5 # seconds
|
|
15
|
+
|
|
16
|
+
def initialize
|
|
17
|
+
super
|
|
18
|
+
@thread = nil
|
|
19
|
+
@xprop_process = nil
|
|
20
|
+
@last_window_id = nil
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def start
|
|
24
|
+
return if @running
|
|
25
|
+
return unless xprop_available?
|
|
26
|
+
|
|
27
|
+
@running = true
|
|
28
|
+
@thread = Thread.new { run_poll_loop }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def stop
|
|
32
|
+
super
|
|
33
|
+
@thread&.kill
|
|
34
|
+
@xprop_process&.close
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def xprop_available?
|
|
40
|
+
system('which xprop > /dev/null 2>&1')
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def run_poll_loop
|
|
44
|
+
while @running
|
|
45
|
+
poll_active_window
|
|
46
|
+
sleep POLL_INTERVAL
|
|
47
|
+
end
|
|
48
|
+
rescue StandardError => e
|
|
49
|
+
RosettAi.logger.error("X11 focus poll error: #{e.message}")
|
|
50
|
+
@running = false
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def poll_active_window
|
|
54
|
+
win_id = active_window_id
|
|
55
|
+
return if win_id == @last_window_id
|
|
56
|
+
return unless win_id
|
|
57
|
+
|
|
58
|
+
@last_window_id = win_id
|
|
59
|
+
app_id, title = window_info(win_id)
|
|
60
|
+
notify_focus_change(app_id, title)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def active_window_id
|
|
64
|
+
output = IO.popen(['xprop', '-root', '_NET_ACTIVE_WINDOW'], err: File::NULL, &:read)
|
|
65
|
+
match = output.match(/window id # (0x[0-9a-f]+)/i)
|
|
66
|
+
match ? match[1] : nil
|
|
67
|
+
rescue Errno::ENOENT
|
|
68
|
+
nil
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def window_info(window_id)
|
|
72
|
+
return ['', ''] unless window_id
|
|
73
|
+
|
|
74
|
+
app_id = extract_wm_class(window_id)
|
|
75
|
+
title = extract_window_title(window_id)
|
|
76
|
+
|
|
77
|
+
[app_id, title]
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def extract_wm_class(window_id)
|
|
81
|
+
output = IO.popen(['xprop', '-id', window_id, 'WM_CLASS'], err: File::NULL, &:read)
|
|
82
|
+
match = output.match(/WM_CLASS\(STRING\) = "([^"]*)", "([^"]*)"/)
|
|
83
|
+
match ? match[2] : ''
|
|
84
|
+
rescue Errno::ENOENT
|
|
85
|
+
''
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def extract_window_title(window_id)
|
|
89
|
+
# Try _NET_WM_NAME first
|
|
90
|
+
output = IO.popen(['xprop', '-id', window_id, '_NET_WM_NAME'], err: File::NULL, &:read)
|
|
91
|
+
if (match = output.match(/_NET_WM_NAME\(UTF8_STRING\) = "(.*)"/))
|
|
92
|
+
return match[1]
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Fallback to WM_NAME
|
|
96
|
+
output = IO.popen(['xprop', '-id', window_id, 'WM_NAME'], err: File::NULL, &:read)
|
|
97
|
+
match = output.match(/WM_NAME\(.*\) = "(.*)"/)
|
|
98
|
+
match ? match[1] : ''
|
|
99
|
+
rescue Errno::ENOENT
|
|
100
|
+
''
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|