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,371 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-only
|
|
4
|
+
# Copyright (C) 2026 Hugo Antonio Sepulveda Manriquez / NeatNerds
|
|
5
|
+
|
|
6
|
+
require_relative 'gui_logger'
|
|
7
|
+
|
|
8
|
+
module RosettAi
|
|
9
|
+
module Desktop
|
|
10
|
+
# Main GTK4/Adwaita application for rai desktop integration.
|
|
11
|
+
#
|
|
12
|
+
# SAFETY PRINCIPLES:
|
|
13
|
+
# 1. All event handlers are wrapped with safe_action() - NEVER crash on user action
|
|
14
|
+
# 2. D-Bus client is defensive - returns safe defaults, never raises
|
|
15
|
+
# 3. Widget construction is wrapped - missing features degrade gracefully
|
|
16
|
+
# 4. Error dialogs inform users instead of crashing
|
|
17
|
+
#
|
|
18
|
+
# This is a standalone app that communicates with rosett-ai exclusively
|
|
19
|
+
# via D-Bus. It does NOT import Ruby Rosett-AI modules for business logic.
|
|
20
|
+
# All operations go through the DbusClient.
|
|
21
|
+
#
|
|
22
|
+
# Follows GNOME HIG: single-window, instant-apply preferences,
|
|
23
|
+
# headerbar with title and buttons.
|
|
24
|
+
class Gtk4App
|
|
25
|
+
APPLICATION_ID = 'be.neatnerds.rosettai'
|
|
26
|
+
UNAVAILABLE_MESSAGE = 'GTK4 app requires the adwaita gem. Install: gem install adwaita'
|
|
27
|
+
|
|
28
|
+
attr_reader :application, :dbus_client
|
|
29
|
+
|
|
30
|
+
def initialize
|
|
31
|
+
@dbus_client = DbusClient.new
|
|
32
|
+
@window = nil
|
|
33
|
+
@application = nil
|
|
34
|
+
@toast_overlay = nil
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Check if GTK4/Adwaita is available.
|
|
38
|
+
#
|
|
39
|
+
# @return [Boolean]
|
|
40
|
+
def self.available?
|
|
41
|
+
require 'adwaita'
|
|
42
|
+
true
|
|
43
|
+
rescue LoadError
|
|
44
|
+
false
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Run the GTK4 application.
|
|
48
|
+
# SAFETY: Wrapped in rescue to show error dialog on startup failure.
|
|
49
|
+
#
|
|
50
|
+
# @param _argv [Array<String>] command-line arguments (ignored)
|
|
51
|
+
# @return [Integer] exit code
|
|
52
|
+
def run(_argv = ARGV)
|
|
53
|
+
GuiLogger.info('Application starting', version: safe_version)
|
|
54
|
+
require_adwaita!
|
|
55
|
+
|
|
56
|
+
@application = Adw::Application.new(APPLICATION_ID, Gio::ApplicationFlags::FLAGS_NONE)
|
|
57
|
+
setup_actions
|
|
58
|
+
@application.signal_connect('activate') { safe_action(:on_activate) }
|
|
59
|
+
@application.signal_connect('shutdown') { GuiLogger.info('Application shutdown') }
|
|
60
|
+
GuiLogger.info('Application run loop starting')
|
|
61
|
+
@application.run([])
|
|
62
|
+
rescue StandardError => e
|
|
63
|
+
# Last resort error handling - show dialog if possible, print to stderr
|
|
64
|
+
show_fatal_error("Application failed to start: #{e.message}")
|
|
65
|
+
1
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
# Execute a block safely, catching all errors and showing dialog.
|
|
71
|
+
# This is the core safety mechanism for all GUI event handlers.
|
|
72
|
+
#
|
|
73
|
+
# @param action_name [Symbol] name of the action for error reporting
|
|
74
|
+
# @yield the action to execute
|
|
75
|
+
def safe_action(action_name)
|
|
76
|
+
GuiLogger.action(action_name) do
|
|
77
|
+
send(action_name)
|
|
78
|
+
end
|
|
79
|
+
rescue StandardError => e
|
|
80
|
+
show_error_dialog('An error occurred', "#{action_name}: #{e.message}")
|
|
81
|
+
log_error("Action #{action_name} failed", e)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def require_adwaita!
|
|
85
|
+
require 'adwaita'
|
|
86
|
+
# Alias Adwaita to Adw for GNOME SDK naming convention
|
|
87
|
+
Object.const_set(:Adw, Adwaita) unless Object.const_defined?(:Adw)
|
|
88
|
+
rescue LoadError
|
|
89
|
+
raise RosettAi::Error, UNAVAILABLE_MESSAGE
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def on_activate
|
|
93
|
+
@window ||= build_main_window
|
|
94
|
+
@window.present
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# ========== Window Building ==========
|
|
98
|
+
|
|
99
|
+
def build_main_window
|
|
100
|
+
window = Adw::ApplicationWindow.new(@application)
|
|
101
|
+
window.title = 'NeatNerds Code Companion'
|
|
102
|
+
window.set_default_size(600, 450)
|
|
103
|
+
|
|
104
|
+
# Main vertical box to hold header + content
|
|
105
|
+
main_box = Gtk::Box.new(:vertical, 0)
|
|
106
|
+
|
|
107
|
+
# Header bar (GNOME HIG style)
|
|
108
|
+
header = build_header_bar
|
|
109
|
+
main_box.append(header)
|
|
110
|
+
|
|
111
|
+
# Toast overlay for notifications (wraps scrolled content)
|
|
112
|
+
@toast_overlay = Adw::ToastOverlay.new
|
|
113
|
+
|
|
114
|
+
# Scrolled content area
|
|
115
|
+
scrolled = Gtk::ScrolledWindow.new
|
|
116
|
+
scrolled.hscrollbar_policy = Gtk::PolicyType::NEVER
|
|
117
|
+
scrolled.vscrollbar_policy = Gtk::PolicyType::AUTOMATIC
|
|
118
|
+
scrolled.vexpand = true
|
|
119
|
+
|
|
120
|
+
# Clamp for responsive width (GNOME HIG)
|
|
121
|
+
clamp = Adw::Clamp.new
|
|
122
|
+
clamp.maximum_size = 600
|
|
123
|
+
clamp.tightening_threshold = 400
|
|
124
|
+
|
|
125
|
+
content = build_main_content
|
|
126
|
+
clamp.child = content
|
|
127
|
+
scrolled.child = clamp
|
|
128
|
+
|
|
129
|
+
@toast_overlay.child = scrolled
|
|
130
|
+
main_box.append(@toast_overlay)
|
|
131
|
+
|
|
132
|
+
window.content = main_box
|
|
133
|
+
window
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def build_header_bar
|
|
137
|
+
header = Adw::HeaderBar.new
|
|
138
|
+
header.title_widget = build_title_widget
|
|
139
|
+
|
|
140
|
+
# Primary menu button (hamburger) on the right
|
|
141
|
+
menu_button = Gtk::MenuButton.new
|
|
142
|
+
menu_button.icon_name = 'open-menu-symbolic'
|
|
143
|
+
menu_button.menu_model = build_app_menu
|
|
144
|
+
# primary= may not exist in older GTK4 versions
|
|
145
|
+
menu_button.primary = true if menu_button.respond_to?(:primary=)
|
|
146
|
+
header.pack_end(menu_button)
|
|
147
|
+
|
|
148
|
+
header
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def build_title_widget
|
|
152
|
+
Adw::WindowTitle.new('NeatNerds Code Companion', '')
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def build_main_content
|
|
156
|
+
box = Gtk::Box.new(:vertical, 12)
|
|
157
|
+
box.margin_start = 24
|
|
158
|
+
box.margin_end = 24
|
|
159
|
+
box.margin_top = 24
|
|
160
|
+
box.margin_bottom = 24
|
|
161
|
+
|
|
162
|
+
# Status group
|
|
163
|
+
status_group = build_status_group
|
|
164
|
+
box.append(status_group)
|
|
165
|
+
|
|
166
|
+
# Actions group
|
|
167
|
+
actions_group = build_actions_group
|
|
168
|
+
box.append(actions_group)
|
|
169
|
+
|
|
170
|
+
box
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def build_status_group
|
|
174
|
+
group = Adw::PreferencesGroup.new
|
|
175
|
+
group.title = 'Service Status'
|
|
176
|
+
|
|
177
|
+
# D-Bus connection status
|
|
178
|
+
dbus_row = Adw::ActionRow.new
|
|
179
|
+
dbus_row.title = 'D-Bus Service'
|
|
180
|
+
dbus_row.subtitle = dbus_status_text
|
|
181
|
+
group.add(dbus_row)
|
|
182
|
+
|
|
183
|
+
# Current context row
|
|
184
|
+
context_row = Adw::ActionRow.new
|
|
185
|
+
context_row.title = 'Current Context'
|
|
186
|
+
context_row.subtitle = current_context_text
|
|
187
|
+
group.add(context_row)
|
|
188
|
+
|
|
189
|
+
# Current focus row
|
|
190
|
+
focus_row = Adw::ActionRow.new
|
|
191
|
+
focus_row.title = 'Active Application'
|
|
192
|
+
focus_row.subtitle = current_focus_text
|
|
193
|
+
group.add(focus_row)
|
|
194
|
+
|
|
195
|
+
group
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def build_actions_group
|
|
199
|
+
group = Adw::PreferencesGroup.new
|
|
200
|
+
group.title = 'Quick Actions'
|
|
201
|
+
|
|
202
|
+
# Compile button
|
|
203
|
+
compile_row = Adw::ActionRow.new
|
|
204
|
+
compile_row.title = 'Compile Rules'
|
|
205
|
+
compile_row.subtitle = 'Run compilation via D-Bus service'
|
|
206
|
+
compile_row.activatable = true
|
|
207
|
+
compile_row.signal_connect('activated') { safe_action(:on_compile_clicked) }
|
|
208
|
+
compile_row.add_suffix(Gtk::Image.new(icon_name: 'go-next-symbolic'))
|
|
209
|
+
group.add(compile_row)
|
|
210
|
+
|
|
211
|
+
group
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def build_app_menu
|
|
215
|
+
menu = Gio::Menu.new
|
|
216
|
+
menu.append('Preferences', 'app.preferences')
|
|
217
|
+
menu.append('About', 'app.about')
|
|
218
|
+
menu.append('Quit', 'app.quit')
|
|
219
|
+
menu
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# ========== Action Setup ==========
|
|
223
|
+
|
|
224
|
+
def setup_actions
|
|
225
|
+
setup_preferences_action
|
|
226
|
+
setup_about_action
|
|
227
|
+
setup_quit_action
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def setup_preferences_action
|
|
231
|
+
prefs_action = Gio::SimpleAction.new('preferences', nil)
|
|
232
|
+
prefs_action.signal_connect('activate') { safe_action(:show_preferences) }
|
|
233
|
+
@application.add_action(prefs_action)
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def setup_about_action
|
|
237
|
+
about_action = Gio::SimpleAction.new('about', nil)
|
|
238
|
+
about_action.signal_connect('activate') { safe_action(:show_about) }
|
|
239
|
+
@application.add_action(about_action)
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def setup_quit_action
|
|
243
|
+
quit_action = Gio::SimpleAction.new('quit', nil)
|
|
244
|
+
quit_action.signal_connect('activate') { @application.quit }
|
|
245
|
+
@application.add_action(quit_action)
|
|
246
|
+
@application.set_accels_for_action('app.quit', ['<Control>q'])
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
# ========== Action Handlers ==========
|
|
250
|
+
|
|
251
|
+
def show_preferences
|
|
252
|
+
dialog = Gtk4Preferences.new(@window, @dbus_client)
|
|
253
|
+
dialog.present
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def show_about
|
|
257
|
+
about = Adw::AboutWindow.new
|
|
258
|
+
about.transient_for = @window
|
|
259
|
+
about.application_name = 'NeatNerds Code Companion'
|
|
260
|
+
about.application_icon = APPLICATION_ID
|
|
261
|
+
about.version = safe_version
|
|
262
|
+
about.developer_name = 'NeatNerds'
|
|
263
|
+
about.website = 'https://neatnerds.be'
|
|
264
|
+
about.issue_url = 'https://github.com/NeatNerds/rosett-ai/issues'
|
|
265
|
+
about.license_type = Gtk::License::GPL_3_0_ONLY
|
|
266
|
+
about.copyright = '2026 Hugo Antonio Sepulveda Manriquez / NeatNerds'
|
|
267
|
+
about.present
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def on_compile_clicked
|
|
271
|
+
unless @dbus_client.service_available?
|
|
272
|
+
show_toast('D-Bus service not available. Start with: rai dbus start')
|
|
273
|
+
return
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
result = @dbus_client.compile
|
|
277
|
+
show_toast(result)
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
# ========== Toast & Dialog Helpers ==========
|
|
281
|
+
|
|
282
|
+
def show_toast(message)
|
|
283
|
+
return unless @toast_overlay
|
|
284
|
+
|
|
285
|
+
toast = Adw::Toast.new(message.to_s)
|
|
286
|
+
toast.timeout = 3
|
|
287
|
+
@toast_overlay.add_toast(toast)
|
|
288
|
+
rescue StandardError => e
|
|
289
|
+
log_error('Toast display failed', e)
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def show_error_dialog(title, message)
|
|
293
|
+
return unless @window
|
|
294
|
+
|
|
295
|
+
dialog = Adw::MessageDialog.new(@window, title, message)
|
|
296
|
+
dialog.add_response('ok', 'OK')
|
|
297
|
+
dialog.default_response = 'ok'
|
|
298
|
+
dialog.present
|
|
299
|
+
rescue StandardError => e
|
|
300
|
+
# If dialog fails, at least log it
|
|
301
|
+
log_error('Error dialog failed', e)
|
|
302
|
+
warn "[rai] #{title}: #{message}"
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
def show_fatal_error(message)
|
|
306
|
+
warn "[rai] FATAL: #{message}"
|
|
307
|
+
# Try to show a basic GTK dialog if possible
|
|
308
|
+
return unless defined?(Gtk) && Gtk.respond_to?(:init)
|
|
309
|
+
|
|
310
|
+
Gtk.init
|
|
311
|
+
dialog = Gtk::MessageDialog.new(
|
|
312
|
+
message: message,
|
|
313
|
+
type: :error,
|
|
314
|
+
buttons: :close
|
|
315
|
+
)
|
|
316
|
+
dialog.run
|
|
317
|
+
dialog.destroy
|
|
318
|
+
rescue StandardError
|
|
319
|
+
# Last resort - just print to stderr
|
|
320
|
+
nil
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
# ========== Status Text Helpers ==========
|
|
324
|
+
# All methods return safe strings, never raise
|
|
325
|
+
|
|
326
|
+
def dbus_status_text
|
|
327
|
+
@dbus_client.service_available? ? 'Connected' : 'Not available'
|
|
328
|
+
rescue StandardError
|
|
329
|
+
'Unknown'
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
def current_context_text
|
|
333
|
+
return 'Service not running' unless @dbus_client.service_available?
|
|
334
|
+
|
|
335
|
+
status = @dbus_client.status
|
|
336
|
+
status['current_context'] || 'default'
|
|
337
|
+
rescue StandardError
|
|
338
|
+
'Unknown'
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
def current_focus_text
|
|
342
|
+
return 'Service not running' unless @dbus_client.service_available?
|
|
343
|
+
|
|
344
|
+
app_id, title = @dbus_client.current_focus
|
|
345
|
+
return 'None' if app_id.nil? || app_id.empty?
|
|
346
|
+
|
|
347
|
+
"#{app_id}: #{title}"
|
|
348
|
+
rescue StandardError
|
|
349
|
+
'Unknown'
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
def safe_version
|
|
353
|
+
defined?(RosettAi::VERSION) ? RosettAi::VERSION : '0.0.0'
|
|
354
|
+
rescue StandardError
|
|
355
|
+
'0.0.0'
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
# ========== Logging ==========
|
|
359
|
+
|
|
360
|
+
def log_error(context, error)
|
|
361
|
+
GuiLogger.error(context, error, component: 'Gtk4App')
|
|
362
|
+
|
|
363
|
+
return unless defined?(RosettAi.logger) && RosettAi.logger
|
|
364
|
+
|
|
365
|
+
RosettAi.logger.error("[Gtk4App] #{context}: #{error.class} - #{error.message}")
|
|
366
|
+
rescue StandardError
|
|
367
|
+
nil
|
|
368
|
+
end
|
|
369
|
+
end
|
|
370
|
+
end
|
|
371
|
+
end
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-only
|
|
4
|
+
# Copyright (C) 2026 Hugo Antonio Sepulveda Manriquez / NeatNerds
|
|
5
|
+
|
|
6
|
+
require_relative 'gui_logger'
|
|
7
|
+
|
|
8
|
+
module RosettAi
|
|
9
|
+
module Desktop
|
|
10
|
+
# Preferences dialog for GTK4 app using AdwPreferencesDialog.
|
|
11
|
+
#
|
|
12
|
+
# SAFETY PRINCIPLES:
|
|
13
|
+
# 1. All callbacks are wrapped with safe_callback() - NEVER crash on user action
|
|
14
|
+
# 2. D-Bus client operations are defensive - always handle failures gracefully
|
|
15
|
+
# 3. Missing libadwaita features degrade gracefully
|
|
16
|
+
# 4. All status queries have rescue blocks
|
|
17
|
+
#
|
|
18
|
+
# Follows GNOME HIG instant-apply pattern - changes are applied
|
|
19
|
+
# immediately via D-Bus, no OK/Apply buttons.
|
|
20
|
+
class Gtk4Preferences
|
|
21
|
+
def initialize(parent_window, dbus_client)
|
|
22
|
+
@parent = parent_window
|
|
23
|
+
@dbus_client = dbus_client
|
|
24
|
+
@dialog = nil
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Build and return the preferences dialog.
|
|
28
|
+
# SAFETY: Returns nil on failure instead of raising.
|
|
29
|
+
#
|
|
30
|
+
# @return [Adw::PreferencesDialog, nil]
|
|
31
|
+
def build
|
|
32
|
+
require 'adwaita'
|
|
33
|
+
Object.const_set(:Adw, Adwaita) unless Object.const_defined?(:Adw)
|
|
34
|
+
|
|
35
|
+
# Try PreferencesDialog first (libadwaita 1.4+), fall back to PreferencesWindow
|
|
36
|
+
@dialog = create_preferences_dialog
|
|
37
|
+
return nil unless @dialog
|
|
38
|
+
|
|
39
|
+
safe_add_page(:add_general_page)
|
|
40
|
+
safe_add_page(:add_context_page)
|
|
41
|
+
safe_add_page(:add_advanced_page)
|
|
42
|
+
|
|
43
|
+
@dialog
|
|
44
|
+
rescue StandardError => e
|
|
45
|
+
log_error('Failed to build preferences dialog', e)
|
|
46
|
+
nil
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Present the preferences dialog.
|
|
50
|
+
# SAFETY: Silently fails if dialog cannot be built.
|
|
51
|
+
def present
|
|
52
|
+
GuiLogger.action(:present_preferences) do
|
|
53
|
+
build unless @dialog
|
|
54
|
+
return unless @dialog
|
|
55
|
+
|
|
56
|
+
# PreferencesDialog (1.4+) uses present(parent), PreferencesWindow (1.0+) uses present()
|
|
57
|
+
# Use defined? check since PreferencesDialog may not exist in older libadwaita
|
|
58
|
+
if defined?(Adw::PreferencesDialog) && @dialog.is_a?(Adw::PreferencesDialog)
|
|
59
|
+
@dialog.present(@parent)
|
|
60
|
+
else
|
|
61
|
+
@dialog.transient_for = @parent if @dialog.respond_to?(:transient_for=)
|
|
62
|
+
@dialog.present
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
rescue StandardError => e
|
|
66
|
+
log_error('Failed to present preferences dialog', e)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
FALLBACK_ENGINES = [
|
|
70
|
+
{ name: 'claude', display_name: 'Claude' },
|
|
71
|
+
{ name: 'generic', display_name: 'Generic' },
|
|
72
|
+
{ name: 'agents_md', display_name: 'AGENTS.md' }
|
|
73
|
+
].freeze
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
# Create preferences dialog with fallback for older libadwaita.
|
|
78
|
+
#
|
|
79
|
+
# @return [Object, nil] PreferencesDialog or PreferencesWindow
|
|
80
|
+
def create_preferences_dialog
|
|
81
|
+
# Try PreferencesDialog first (libadwaita 1.4+)
|
|
82
|
+
if defined?(Adw::PreferencesDialog)
|
|
83
|
+
dialog = Adw::PreferencesDialog.new
|
|
84
|
+
dialog.title = 'Preferences'
|
|
85
|
+
return dialog
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Fall back to PreferencesWindow (libadwaita 1.0+)
|
|
89
|
+
if defined?(Adw::PreferencesWindow)
|
|
90
|
+
window = Adw::PreferencesWindow.new
|
|
91
|
+
window.title = 'Preferences'
|
|
92
|
+
return window
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
log_error('No preferences dialog available', StandardError.new('libadwaita too old'))
|
|
96
|
+
nil
|
|
97
|
+
rescue StandardError => e
|
|
98
|
+
log_error('Failed to create preferences dialog', e)
|
|
99
|
+
nil
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Safely add a page, catching any errors.
|
|
103
|
+
#
|
|
104
|
+
# @param method_name [Symbol] page builder method to call
|
|
105
|
+
def safe_add_page(method_name)
|
|
106
|
+
send(method_name)
|
|
107
|
+
rescue StandardError => e
|
|
108
|
+
log_error("Failed to add page #{method_name}", e)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Wrap callback execution with error handling.
|
|
112
|
+
#
|
|
113
|
+
# @param name [String] callback name for logging
|
|
114
|
+
# @yield the callback block
|
|
115
|
+
def safe_callback(name)
|
|
116
|
+
GuiLogger.callback(name, component: 'Gtk4Preferences')
|
|
117
|
+
yield
|
|
118
|
+
rescue StandardError => e
|
|
119
|
+
log_error("Callback #{name} failed", e)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def add_general_page
|
|
123
|
+
page = Adw::PreferencesPage.new
|
|
124
|
+
page.title = 'General'
|
|
125
|
+
page.icon_name = 'preferences-system-symbolic'
|
|
126
|
+
|
|
127
|
+
# Compilation group
|
|
128
|
+
compile_group = Adw::PreferencesGroup.new
|
|
129
|
+
compile_group.title = 'Compilation'
|
|
130
|
+
compile_group.description = 'Configure rule compilation behaviour'
|
|
131
|
+
|
|
132
|
+
# Verbose mode switch
|
|
133
|
+
verbose_row = create_switch_row(
|
|
134
|
+
'Verbose Output',
|
|
135
|
+
'Show detailed compilation messages'
|
|
136
|
+
) { |active| safe_callback('verbose') { on_verbose_changed(active) } }
|
|
137
|
+
compile_group.add(verbose_row) if verbose_row
|
|
138
|
+
|
|
139
|
+
# Strict mode switch
|
|
140
|
+
strict_row = create_switch_row(
|
|
141
|
+
'Strict Mode',
|
|
142
|
+
'Treat warnings as errors'
|
|
143
|
+
) { |active| safe_callback('strict') { on_strict_changed(active) } }
|
|
144
|
+
compile_group.add(strict_row) if strict_row
|
|
145
|
+
|
|
146
|
+
page.add(compile_group)
|
|
147
|
+
|
|
148
|
+
# Engine group
|
|
149
|
+
engine_group = Adw::PreferencesGroup.new
|
|
150
|
+
engine_group.title = 'Engine'
|
|
151
|
+
engine_group.description = 'Default compilation engine'
|
|
152
|
+
|
|
153
|
+
engine_row = create_combo_row(
|
|
154
|
+
'Default Engine',
|
|
155
|
+
'Engine used when not specified',
|
|
156
|
+
engine_display_names
|
|
157
|
+
) { |index| safe_callback('engine') { on_engine_changed(index) } }
|
|
158
|
+
engine_group.add(engine_row) if engine_row
|
|
159
|
+
|
|
160
|
+
page.add(engine_group)
|
|
161
|
+
@dialog.add(page)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def add_context_page
|
|
165
|
+
page = Adw::PreferencesPage.new
|
|
166
|
+
page.title = 'Contexts'
|
|
167
|
+
page.icon_name = 'view-list-symbolic'
|
|
168
|
+
|
|
169
|
+
# Current context group
|
|
170
|
+
context_group = Adw::PreferencesGroup.new
|
|
171
|
+
context_group.title = 'Context Switching'
|
|
172
|
+
context_group.description = 'AI context configuration'
|
|
173
|
+
|
|
174
|
+
# Auto-switch toggle
|
|
175
|
+
auto_switch_row = create_switch_row(
|
|
176
|
+
'Automatic Context Switching',
|
|
177
|
+
'Switch context based on focused application'
|
|
178
|
+
) { |active| safe_callback('auto_switch') { on_auto_switch_changed(active) } }
|
|
179
|
+
context_group.add(auto_switch_row) if auto_switch_row
|
|
180
|
+
|
|
181
|
+
page.add(context_group)
|
|
182
|
+
@dialog.add(page)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def add_advanced_page
|
|
186
|
+
page = Adw::PreferencesPage.new
|
|
187
|
+
page.title = 'Advanced'
|
|
188
|
+
page.icon_name = 'applications-engineering-symbolic'
|
|
189
|
+
|
|
190
|
+
# D-Bus group
|
|
191
|
+
dbus_group = Adw::PreferencesGroup.new
|
|
192
|
+
dbus_group.title = 'D-Bus Service'
|
|
193
|
+
dbus_group.description = 'Service connection settings'
|
|
194
|
+
|
|
195
|
+
# Status row
|
|
196
|
+
status_row = Adw::ActionRow.new
|
|
197
|
+
status_row.title = 'Service Status'
|
|
198
|
+
status_row.subtitle = service_status_text
|
|
199
|
+
dbus_group.add(status_row)
|
|
200
|
+
|
|
201
|
+
# Reconnect button
|
|
202
|
+
reconnect_row = Adw::ActionRow.new
|
|
203
|
+
reconnect_row.title = 'Reconnect'
|
|
204
|
+
reconnect_row.subtitle = 'Reconnect to D-Bus service'
|
|
205
|
+
reconnect_row.activatable = true
|
|
206
|
+
reconnect_row.signal_connect('activated') do
|
|
207
|
+
safe_callback('reconnect') { on_reconnect_clicked }
|
|
208
|
+
end
|
|
209
|
+
reconnect_row.add_suffix(Gtk::Image.new(icon_name: 'view-refresh-symbolic'))
|
|
210
|
+
dbus_group.add(reconnect_row)
|
|
211
|
+
|
|
212
|
+
page.add(dbus_group)
|
|
213
|
+
@dialog.add(page)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# ========== Widget Factories ==========
|
|
217
|
+
# These handle missing widget types gracefully
|
|
218
|
+
|
|
219
|
+
def create_switch_row(title, subtitle, &block)
|
|
220
|
+
return nil unless defined?(Adw::SwitchRow)
|
|
221
|
+
|
|
222
|
+
row = Adw::SwitchRow.new
|
|
223
|
+
row.title = title
|
|
224
|
+
row.subtitle = subtitle
|
|
225
|
+
row.signal_connect('notify::active') { |r| yield(r.active?) } if block
|
|
226
|
+
row
|
|
227
|
+
rescue StandardError => e
|
|
228
|
+
log_error("Failed to create switch row '#{title}'", e)
|
|
229
|
+
nil
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def create_combo_row(title, subtitle, options, &block)
|
|
233
|
+
return nil unless defined?(Adw::ComboRow)
|
|
234
|
+
|
|
235
|
+
row = Adw::ComboRow.new
|
|
236
|
+
row.title = title
|
|
237
|
+
row.subtitle = subtitle
|
|
238
|
+
model = Gtk::StringList.new(options)
|
|
239
|
+
row.model = model
|
|
240
|
+
row.signal_connect('notify::selected') { |r| yield(r.selected) } if block
|
|
241
|
+
row
|
|
242
|
+
rescue StandardError => e
|
|
243
|
+
log_error("Failed to create combo row '#{title}'", e)
|
|
244
|
+
nil
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# ========== Engine Helpers ==========
|
|
248
|
+
|
|
249
|
+
def engine_list
|
|
250
|
+
engines = @dbus_client.list_engines
|
|
251
|
+
engines.empty? ? FALLBACK_ENGINES : engines
|
|
252
|
+
rescue StandardError
|
|
253
|
+
FALLBACK_ENGINES
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def engine_display_names
|
|
257
|
+
engine_list.map { |e| e[:display_name] || e['display_name'] || e[:name] || e['name'] }
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def engine_names
|
|
261
|
+
engine_list.map { |e| e[:name] || e['name'] }
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
# ========== Status Helpers ==========
|
|
265
|
+
|
|
266
|
+
def service_status_text
|
|
267
|
+
return 'Not connected' unless @dbus_client.service_available?
|
|
268
|
+
|
|
269
|
+
status = @dbus_client.status
|
|
270
|
+
version = status['version'] || 'unknown'
|
|
271
|
+
"Connected (v#{version})"
|
|
272
|
+
rescue StandardError
|
|
273
|
+
'Unknown'
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# ========== Callbacks ==========
|
|
277
|
+
# These are called via safe_callback, so they can safely log/update state
|
|
278
|
+
|
|
279
|
+
def on_verbose_changed(active)
|
|
280
|
+
log_debug("Verbose mode: #{active}")
|
|
281
|
+
@dbus_client.set_config('verbose', active.to_s)
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
def on_strict_changed(active)
|
|
285
|
+
log_debug("Strict mode: #{active}")
|
|
286
|
+
@dbus_client.set_config('strict', active.to_s)
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
def on_engine_changed(index)
|
|
290
|
+
names = engine_names
|
|
291
|
+
engine = names[index] || 'claude'
|
|
292
|
+
log_debug("Engine changed: #{engine}")
|
|
293
|
+
@dbus_client.set_config('default_engine', engine)
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
def on_auto_switch_changed(active)
|
|
297
|
+
log_debug("Auto-switch: #{active}")
|
|
298
|
+
@dbus_client.set_config('auto_switch', active.to_s)
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
def on_reconnect_clicked
|
|
302
|
+
@dbus_client.disconnect
|
|
303
|
+
connected = @dbus_client.connect
|
|
304
|
+
log_debug("Reconnect attempt: #{connected ? 'success' : 'failed'}")
|
|
305
|
+
# Would update status row when we have a reference to it
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
# ========== Logging ==========
|
|
309
|
+
|
|
310
|
+
def log_error(context, error)
|
|
311
|
+
GuiLogger.error(context, error, component: 'Gtk4Preferences')
|
|
312
|
+
|
|
313
|
+
return unless defined?(RosettAi.logger) && RosettAi.logger
|
|
314
|
+
|
|
315
|
+
RosettAi.logger.error("[Gtk4Preferences] #{context}: #{error.class} - #{error.message}")
|
|
316
|
+
rescue StandardError
|
|
317
|
+
nil
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
def log_debug(message)
|
|
321
|
+
GuiLogger.debug(message, component: 'Gtk4Preferences')
|
|
322
|
+
|
|
323
|
+
return unless defined?(RosettAi.logger) && RosettAi.logger
|
|
324
|
+
|
|
325
|
+
RosettAi.logger.debug("[Gtk4Preferences] #{message}")
|
|
326
|
+
rescue StandardError
|
|
327
|
+
nil
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
end
|