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,236 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-only
|
|
4
|
+
# Copyright (C) 2026 Hugo Antonio Sepulveda Manriquez / NeatNerds
|
|
5
|
+
|
|
6
|
+
require 'json'
|
|
7
|
+
require 'time'
|
|
8
|
+
|
|
9
|
+
module RosettAi
|
|
10
|
+
module Desktop
|
|
11
|
+
# Structured GUI action logger for debugging and crash analysis.
|
|
12
|
+
#
|
|
13
|
+
# Provides comprehensive logging of all GUI interactions including:
|
|
14
|
+
# - Action invocation with parameters
|
|
15
|
+
# - Success/failure outcomes with duration
|
|
16
|
+
# - Widget events and state changes
|
|
17
|
+
# - D-Bus operations
|
|
18
|
+
#
|
|
19
|
+
# Log output is controlled by environment variables:
|
|
20
|
+
# RAI_GUI_LOG=1 Enable logging to stderr
|
|
21
|
+
# RAI_GUI_LOG_FILE=path Also log to file
|
|
22
|
+
# RAI_GUI_LOG_LEVEL=debug Set log level (debug, info, warn, error)
|
|
23
|
+
#
|
|
24
|
+
# Log format is JSON Lines for easy parsing and analysis.
|
|
25
|
+
#
|
|
26
|
+
# @example Enable logging
|
|
27
|
+
# RAI_GUI_LOG=1 bin/raictl desktop gtk4
|
|
28
|
+
#
|
|
29
|
+
# @example Log to file
|
|
30
|
+
# RAI_GUI_LOG=1 RAI_GUI_LOG_FILE=/tmp/rosett-ai-gui.log bin/raictl desktop gtk4
|
|
31
|
+
#
|
|
32
|
+
class GuiLogger
|
|
33
|
+
LEVELS = { debug: 0, info: 1, warn: 2, error: 3 }.freeze
|
|
34
|
+
DEFAULT_LEVEL = :info
|
|
35
|
+
|
|
36
|
+
class << self
|
|
37
|
+
# Get the singleton instance.
|
|
38
|
+
#
|
|
39
|
+
# @return [GuiLogger]
|
|
40
|
+
def instance
|
|
41
|
+
@instance ||= new
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Delegate class methods to instance for convenience.
|
|
45
|
+
[:action, :action_start, :action_end, :callback, :widget_event, :dbus_call, :debug, :info, :warn, :error,
|
|
46
|
+
:enabled?].each do |method|
|
|
47
|
+
define_method(method) { |*args, **kwargs, &block| instance.send(method, *args, **kwargs, &block) }
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def initialize
|
|
52
|
+
@enabled = ENV['RAI_GUI_LOG'] == '1'
|
|
53
|
+
@log_file = ENV.fetch('RAI_GUI_LOG_FILE', nil)
|
|
54
|
+
@level = parse_level(ENV.fetch('RAI_GUI_LOG_LEVEL', 'info'))
|
|
55
|
+
@file_handle = nil
|
|
56
|
+
@action_stack = []
|
|
57
|
+
@mutex = Mutex.new
|
|
58
|
+
|
|
59
|
+
setup_file_logging if @log_file
|
|
60
|
+
at_exit { cleanup }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Check if logging is enabled.
|
|
64
|
+
#
|
|
65
|
+
# @return [Boolean]
|
|
66
|
+
def enabled?
|
|
67
|
+
@enabled
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Log a complete action with automatic timing.
|
|
71
|
+
#
|
|
72
|
+
# @param name [String, Symbol] action name
|
|
73
|
+
# @param context [Hash] additional context
|
|
74
|
+
# @yield the action block
|
|
75
|
+
# @return [Object] result of the block
|
|
76
|
+
def action(name, context = {})
|
|
77
|
+
return yield unless @enabled
|
|
78
|
+
|
|
79
|
+
action_start(name, context)
|
|
80
|
+
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
81
|
+
|
|
82
|
+
begin
|
|
83
|
+
result = yield
|
|
84
|
+
duration = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time) * 1000).round(2)
|
|
85
|
+
action_end(name, status: :success, duration_ms: duration)
|
|
86
|
+
result
|
|
87
|
+
rescue StandardError => e
|
|
88
|
+
duration = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time) * 1000).round(2)
|
|
89
|
+
action_end(name, status: :error, duration_ms: duration, error: e.class.name, message: e.message)
|
|
90
|
+
raise
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Log action start.
|
|
95
|
+
#
|
|
96
|
+
# @param name [String, Symbol] action name
|
|
97
|
+
# @param context [Hash] additional context
|
|
98
|
+
def action_start(name, context = {})
|
|
99
|
+
return unless @enabled
|
|
100
|
+
|
|
101
|
+
@mutex.synchronize { @action_stack.push(name.to_s) }
|
|
102
|
+
write_log(:info, 'action_start', { action: name.to_s }.merge(context))
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Log action end.
|
|
106
|
+
#
|
|
107
|
+
# @param name [String, Symbol] action name
|
|
108
|
+
# @param context [Hash] additional context (status, duration_ms, error)
|
|
109
|
+
def action_end(name, context = {})
|
|
110
|
+
return unless @enabled
|
|
111
|
+
|
|
112
|
+
@mutex.synchronize { @action_stack.pop }
|
|
113
|
+
level = context[:status] == :error ? :error : :info
|
|
114
|
+
write_log(level, 'action_end', { action: name.to_s }.merge(context))
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Log a callback invocation.
|
|
118
|
+
#
|
|
119
|
+
# @param name [String, Symbol] callback name
|
|
120
|
+
# @param context [Hash] additional context
|
|
121
|
+
def callback(name, context = {})
|
|
122
|
+
return unless @enabled
|
|
123
|
+
|
|
124
|
+
write_log(:debug, 'callback', { callback: name.to_s }.merge(context))
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Log a widget event.
|
|
128
|
+
#
|
|
129
|
+
# @param widget_type [String, Symbol] widget type (e.g., :switch_row, :combo_row)
|
|
130
|
+
# @param event [String, Symbol] event name (e.g., :toggled, :selected)
|
|
131
|
+
# @param context [Hash] additional context (value, widget_id, etc.)
|
|
132
|
+
def widget_event(widget_type, event, context = {})
|
|
133
|
+
return unless @enabled
|
|
134
|
+
|
|
135
|
+
write_log(:debug, 'widget_event', {
|
|
136
|
+
widget: widget_type.to_s,
|
|
137
|
+
event: event.to_s
|
|
138
|
+
}.merge(context))
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Log a D-Bus operation.
|
|
142
|
+
#
|
|
143
|
+
# @param operation [String, Symbol] operation name
|
|
144
|
+
# @param context [Hash] additional context
|
|
145
|
+
def dbus_call(operation, context = {})
|
|
146
|
+
return unless @enabled
|
|
147
|
+
|
|
148
|
+
write_log(:debug, 'dbus_call', { operation: operation.to_s }.merge(context))
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Log debug message.
|
|
152
|
+
#
|
|
153
|
+
# @param message [String] log message
|
|
154
|
+
# @param context [Hash] additional context
|
|
155
|
+
def debug(message, context = {})
|
|
156
|
+
write_log(:debug, 'debug', { message: message }.merge(context))
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Log info message.
|
|
160
|
+
#
|
|
161
|
+
# @param message [String] log message
|
|
162
|
+
# @param context [Hash] additional context
|
|
163
|
+
def info(message, context = {})
|
|
164
|
+
write_log(:info, 'info', { message: message }.merge(context))
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Log warning message.
|
|
168
|
+
#
|
|
169
|
+
# @param message [String] log message
|
|
170
|
+
# @param context [Hash] additional context
|
|
171
|
+
def warn(message, context = {})
|
|
172
|
+
write_log(:warn, 'warn', { message: message }.merge(context))
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Log error message.
|
|
176
|
+
#
|
|
177
|
+
# @param message [String] log message
|
|
178
|
+
# @param error [Exception, nil] optional exception
|
|
179
|
+
# @param context [Hash] additional context
|
|
180
|
+
def error(message, error = nil, context = {})
|
|
181
|
+
data = { message: message }.merge(context)
|
|
182
|
+
if error
|
|
183
|
+
data[:error_class] = error.class.name
|
|
184
|
+
data[:error_message] = error.message
|
|
185
|
+
data[:backtrace] = error.backtrace&.first(5)
|
|
186
|
+
end
|
|
187
|
+
write_log(:error, 'error', data)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
private
|
|
191
|
+
|
|
192
|
+
def parse_level(level_str)
|
|
193
|
+
LEVELS.fetch(level_str.to_sym, LEVELS[DEFAULT_LEVEL])
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def setup_file_logging
|
|
197
|
+
@file_handle = File.open(@log_file, 'a') # rubocop:disable Style/FileOpen -- persistent handle, closed in #cleanup
|
|
198
|
+
@file_handle.sync = true
|
|
199
|
+
rescue StandardError => e
|
|
200
|
+
warn "[GuiLogger] Failed to open log file: #{e.message}"
|
|
201
|
+
@file_handle = nil
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def cleanup
|
|
205
|
+
@file_handle&.close
|
|
206
|
+
rescue StandardError
|
|
207
|
+
nil
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def write_log(level, event_type, data)
|
|
211
|
+
return unless @enabled
|
|
212
|
+
return if LEVELS.fetch(level, 0) < @level
|
|
213
|
+
|
|
214
|
+
entry = {
|
|
215
|
+
timestamp: Time.now.utc.iso8601(3),
|
|
216
|
+
level: level.to_s,
|
|
217
|
+
event: event_type,
|
|
218
|
+
pid: Process.pid
|
|
219
|
+
}.merge(data)
|
|
220
|
+
|
|
221
|
+
# Add action context if within an action
|
|
222
|
+
entry[:action_stack] = @action_stack.dup unless @action_stack.empty?
|
|
223
|
+
|
|
224
|
+
json_line = JSON.generate(entry)
|
|
225
|
+
|
|
226
|
+
@mutex.synchronize do
|
|
227
|
+
warn json_line
|
|
228
|
+
@file_handle&.puts(json_line)
|
|
229
|
+
end
|
|
230
|
+
rescue StandardError
|
|
231
|
+
# Never let logging cause issues
|
|
232
|
+
nil
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
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 Doctor
|
|
8
|
+
# Base interface for diagnostic checks.
|
|
9
|
+
#
|
|
10
|
+
# Include this module and implement #perform to create a check.
|
|
11
|
+
# The #run method calls #perform and sets status/message/remediation.
|
|
12
|
+
#
|
|
13
|
+
# @author hugo
|
|
14
|
+
# @author claude
|
|
15
|
+
module Check
|
|
16
|
+
# @return [String] human-readable check name
|
|
17
|
+
def name
|
|
18
|
+
self.class.check_name
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# @return [Symbol] :pass, :warn, or :fail
|
|
22
|
+
attr_reader :status
|
|
23
|
+
|
|
24
|
+
# @return [String] result description
|
|
25
|
+
attr_reader :message
|
|
26
|
+
|
|
27
|
+
# @return [String, nil] remediation steps (nil when passing)
|
|
28
|
+
attr_reader :remediation
|
|
29
|
+
|
|
30
|
+
# Execute the check. Sets status, message, and remediation.
|
|
31
|
+
# @return [void]
|
|
32
|
+
def run
|
|
33
|
+
perform
|
|
34
|
+
rescue StandardError => e
|
|
35
|
+
fail!(e.message, remediation: ::I18n.t('rosett_ai.doctor.unexpected_error'))
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Register this check class with the Doctor module.
|
|
39
|
+
# @param check_name [String] unique check identifier
|
|
40
|
+
def self.included(base)
|
|
41
|
+
base.extend(ClassMethods)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Class-level methods for check classes.
|
|
45
|
+
module ClassMethods
|
|
46
|
+
# Set or get the check name.
|
|
47
|
+
# @param name [String, nil] check name to set
|
|
48
|
+
# @return [String]
|
|
49
|
+
def check_name(name = nil)
|
|
50
|
+
@check_name = name if name
|
|
51
|
+
@check_name || (self.name || 'anonymous').split('::').last.gsub(/Check$/, '').gsub(/([A-Z])/, '_\1').sub(
|
|
52
|
+
/^_/, ''
|
|
53
|
+
).downcase
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
# Mark the check as passing.
|
|
60
|
+
# @param message [String]
|
|
61
|
+
def pass!(message)
|
|
62
|
+
@status = :pass
|
|
63
|
+
@message = message
|
|
64
|
+
@remediation = nil
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Mark the check as warning.
|
|
68
|
+
# @param message [String]
|
|
69
|
+
# @param remediation [String]
|
|
70
|
+
def warn!(message, remediation:)
|
|
71
|
+
@status = :warn
|
|
72
|
+
@message = message
|
|
73
|
+
@remediation = remediation
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Mark the check as failed.
|
|
77
|
+
# @param message [String]
|
|
78
|
+
# @param remediation [String]
|
|
79
|
+
def fail!(message, remediation:)
|
|
80
|
+
@status = :fail
|
|
81
|
+
@message = message
|
|
82
|
+
@remediation = remediation
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Subclasses must implement this method.
|
|
86
|
+
# @abstract
|
|
87
|
+
def perform
|
|
88
|
+
raise NotImplementedError, "#{self.class}#perform must be implemented"
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
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 Doctor
|
|
8
|
+
module Checks
|
|
9
|
+
# Verifies the cache directory exists and is writable.
|
|
10
|
+
#
|
|
11
|
+
# @author hugo
|
|
12
|
+
# @author claude
|
|
13
|
+
class CacheHealthCheck
|
|
14
|
+
include Check
|
|
15
|
+
|
|
16
|
+
check_name 'cache_health'
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def perform
|
|
21
|
+
cache_dir = resolve_cache_dir
|
|
22
|
+
|
|
23
|
+
unless cache_dir.exist?
|
|
24
|
+
warn!(
|
|
25
|
+
::I18n.t('rosett_ai.doctor.cache_health.not_found', path: cache_dir),
|
|
26
|
+
remediation: ::I18n.t('rosett_ai.doctor.cache_health.create_dir', path: cache_dir)
|
|
27
|
+
)
|
|
28
|
+
return
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
if cache_dir.writable?
|
|
32
|
+
pass!(::I18n.t('rosett_ai.doctor.cache_health.writable', path: cache_dir))
|
|
33
|
+
else
|
|
34
|
+
fail!(
|
|
35
|
+
::I18n.t('rosett_ai.doctor.cache_health.not_writable', path: cache_dir),
|
|
36
|
+
remediation: ::I18n.t('rosett_ai.doctor.cache_health.fix_permissions', path: cache_dir)
|
|
37
|
+
)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def resolve_cache_dir
|
|
42
|
+
xdg = ENV.fetch('XDG_CACHE_HOME', File.join(Dir.home, '.cache'))
|
|
43
|
+
Pathname.new(xdg).join('rosett-ai')
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
RosettAi::Doctor.register(RosettAi::Doctor::Checks::CacheHealthCheck)
|
|
@@ -0,0 +1,39 @@
|
|
|
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 Doctor
|
|
8
|
+
module Checks
|
|
9
|
+
# Checks whether the D-Bus session bus is available.
|
|
10
|
+
#
|
|
11
|
+
# D-Bus is optional — missing D-Bus is a warning, not a failure.
|
|
12
|
+
#
|
|
13
|
+
# @author hugo
|
|
14
|
+
# @author claude
|
|
15
|
+
class DbusAvailabilityCheck
|
|
16
|
+
include Check
|
|
17
|
+
|
|
18
|
+
check_name 'dbus_availability'
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def perform
|
|
23
|
+
bus_address = ENV.fetch('DBUS_SESSION_BUS_ADDRESS', nil)
|
|
24
|
+
|
|
25
|
+
if bus_address && !bus_address.empty?
|
|
26
|
+
pass!(::I18n.t('rosett_ai.doctor.dbus_availability.available'))
|
|
27
|
+
else
|
|
28
|
+
warn!(
|
|
29
|
+
::I18n.t('rosett_ai.doctor.dbus_availability.unavailable'),
|
|
30
|
+
remediation: ::I18n.t('rosett_ai.doctor.dbus_availability.install_dbus')
|
|
31
|
+
)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
RosettAi::Doctor.register(RosettAi::Doctor::Checks::DbusAvailabilityCheck)
|
|
@@ -0,0 +1,46 @@
|
|
|
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 Doctor
|
|
8
|
+
module Checks
|
|
9
|
+
# Detects installed rai engines.
|
|
10
|
+
#
|
|
11
|
+
# No engines is a warning (rosett-ai still works for validation/compilation
|
|
12
|
+
# with the generic target).
|
|
13
|
+
#
|
|
14
|
+
# @author hugo
|
|
15
|
+
# @author claude
|
|
16
|
+
class EngineDetectionCheck
|
|
17
|
+
include Check
|
|
18
|
+
|
|
19
|
+
check_name 'engine_detection'
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def perform
|
|
24
|
+
engines = discover_engines
|
|
25
|
+
|
|
26
|
+
if engines.empty?
|
|
27
|
+
warn!(
|
|
28
|
+
::I18n.t('rosett_ai.doctor.engine_detection.none_found'),
|
|
29
|
+
remediation: ::I18n.t('rosett_ai.doctor.engine_detection.install_engine')
|
|
30
|
+
)
|
|
31
|
+
else
|
|
32
|
+
pass!(::I18n.t('rosett_ai.doctor.engine_detection.found', engines: engines.join(', ')))
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def discover_engines
|
|
37
|
+
RosettAi::Plugins::Registry.available(:engine)
|
|
38
|
+
rescue StandardError
|
|
39
|
+
[]
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
RosettAi::Doctor.register(RosettAi::Doctor::Checks::EngineDetectionCheck)
|
|
@@ -0,0 +1,44 @@
|
|
|
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 Doctor
|
|
8
|
+
module Checks
|
|
9
|
+
# Verifies key directories are writable.
|
|
10
|
+
#
|
|
11
|
+
# @author hugo
|
|
12
|
+
# @author claude
|
|
13
|
+
class FilePermissionCheck
|
|
14
|
+
include Check
|
|
15
|
+
|
|
16
|
+
check_name 'file_permissions'
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def perform
|
|
21
|
+
resolver = RosettAi::PathResolver.new
|
|
22
|
+
dirs = [resolver.global_dir, resolver.rai_config_dir]
|
|
23
|
+
problems = dirs.each_with_object([]) do |dir, acc|
|
|
24
|
+
next unless dir.exist?
|
|
25
|
+
next if dir.writable?
|
|
26
|
+
|
|
27
|
+
acc << dir.to_s
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
if problems.empty?
|
|
31
|
+
pass!(::I18n.t('rosett_ai.doctor.file_permissions.writable'))
|
|
32
|
+
else
|
|
33
|
+
fail!(
|
|
34
|
+
::I18n.t('rosett_ai.doctor.file_permissions.not_writable', dirs: problems.join(', ')),
|
|
35
|
+
remediation: ::I18n.t('rosett_ai.doctor.file_permissions.fix_permissions')
|
|
36
|
+
)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
RosettAi::Doctor.register(RosettAi::Doctor::Checks::FilePermissionCheck)
|
|
@@ -0,0 +1,55 @@
|
|
|
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 Doctor
|
|
8
|
+
module Checks
|
|
9
|
+
# Verifies all Gemfile.lock dependencies are installed.
|
|
10
|
+
#
|
|
11
|
+
# @author hugo
|
|
12
|
+
# @author claude
|
|
13
|
+
class GemDependencyCheck
|
|
14
|
+
include Check
|
|
15
|
+
|
|
16
|
+
check_name 'gem_dependencies'
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def perform
|
|
21
|
+
lockfile = RosettAi.root.join('Gemfile.lock')
|
|
22
|
+
|
|
23
|
+
unless lockfile.exist?
|
|
24
|
+
warn!(
|
|
25
|
+
::I18n.t('rosett_ai.doctor.gem_dependencies.no_lockfile'),
|
|
26
|
+
remediation: ::I18n.t('rosett_ai.doctor.gem_dependencies.run_bundle')
|
|
27
|
+
)
|
|
28
|
+
return
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
missing = find_missing_gems
|
|
32
|
+
if missing.empty?
|
|
33
|
+
pass!(::I18n.t('rosett_ai.doctor.gem_dependencies.all_installed',
|
|
34
|
+
count: Gem.loaded_specs.size))
|
|
35
|
+
else
|
|
36
|
+
fail!(
|
|
37
|
+
::I18n.t('rosett_ai.doctor.gem_dependencies.missing', gems: missing.join(', ')),
|
|
38
|
+
remediation: ::I18n.t('rosett_ai.doctor.gem_dependencies.run_bundle')
|
|
39
|
+
)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def find_missing_gems
|
|
44
|
+
Bundler.locked_gems.specs.each_with_object([]) do |spec, missing|
|
|
45
|
+
missing << spec.name unless Gem.loaded_specs.key?(spec.name)
|
|
46
|
+
end
|
|
47
|
+
rescue StandardError
|
|
48
|
+
[]
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
RosettAi::Doctor.register(RosettAi::Doctor::Checks::GemDependencyCheck)
|
|
@@ -0,0 +1,50 @@
|
|
|
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 Doctor
|
|
8
|
+
module Checks
|
|
9
|
+
# Verifies the running Ruby version matches .ruby-version.
|
|
10
|
+
#
|
|
11
|
+
# @author hugo
|
|
12
|
+
# @author claude
|
|
13
|
+
class RubyVersionCheck
|
|
14
|
+
include Check
|
|
15
|
+
|
|
16
|
+
check_name 'ruby_version'
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def perform
|
|
21
|
+
expected = read_ruby_version
|
|
22
|
+
actual = RUBY_VERSION
|
|
23
|
+
|
|
24
|
+
if expected.nil?
|
|
25
|
+
warn!(
|
|
26
|
+
::I18n.t('rosett_ai.doctor.ruby_version.no_file'),
|
|
27
|
+
remediation: ::I18n.t('rosett_ai.doctor.ruby_version.create_file')
|
|
28
|
+
)
|
|
29
|
+
elsif actual == expected
|
|
30
|
+
pass!(::I18n.t('rosett_ai.doctor.ruby_version.match', version: actual))
|
|
31
|
+
else
|
|
32
|
+
fail!(
|
|
33
|
+
::I18n.t('rosett_ai.doctor.ruby_version.mismatch', actual: actual, expected: expected),
|
|
34
|
+
remediation: ::I18n.t('rosett_ai.doctor.ruby_version.install', version: expected)
|
|
35
|
+
)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def read_ruby_version
|
|
40
|
+
path = RosettAi.root.join('.ruby-version')
|
|
41
|
+
return nil unless path.exist?
|
|
42
|
+
|
|
43
|
+
path.read.strip
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
RosettAi::Doctor.register(RosettAi::Doctor::Checks::RubyVersionCheck)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-only
|
|
4
|
+
# Copyright (C) 2026 Hugo Antonio Sepulveda Manriquez / NeatNerds
|
|
5
|
+
|
|
6
|
+
module RosettAi
|
|
7
|
+
module Doctor
|
|
8
|
+
module Checks
|
|
9
|
+
# Warns when a stale ~/.config/nncc/ directory exists.
|
|
10
|
+
#
|
|
11
|
+
# The legacy XDG config path was ~/.config/nncc/ before the rename to
|
|
12
|
+
# rosett-ai. This check detects the stale directory and recommends
|
|
13
|
+
# running `raictl migrate` to move the config.
|
|
14
|
+
#
|
|
15
|
+
# @author hugo
|
|
16
|
+
# @author claude
|
|
17
|
+
class StaleConfigNnccCheck
|
|
18
|
+
include Check
|
|
19
|
+
|
|
20
|
+
check_name 'stale_config_nncc'
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def perform
|
|
25
|
+
config_nncc = Pathname.new(Dir.home).join('.config', 'nncc')
|
|
26
|
+
|
|
27
|
+
unless config_nncc.directory?
|
|
28
|
+
pass!(::I18n.t('rosett_ai.doctor.stale_config_nncc.clean'))
|
|
29
|
+
return
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
warn_stale(config_nncc)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def warn_stale(config_nncc)
|
|
36
|
+
path_str = config_nncc.to_s
|
|
37
|
+
remediation = ::I18n.t('rosett_ai.doctor.stale_config_nncc.migrate')
|
|
38
|
+
children = config_nncc.children
|
|
39
|
+
|
|
40
|
+
if children.any?
|
|
41
|
+
warn!(
|
|
42
|
+
::I18n.t('rosett_ai.doctor.stale_config_nncc.found', path: path_str, count: children.count),
|
|
43
|
+
remediation: remediation
|
|
44
|
+
)
|
|
45
|
+
else
|
|
46
|
+
warn!(
|
|
47
|
+
::I18n.t('rosett_ai.doctor.stale_config_nncc.empty', path: path_str),
|
|
48
|
+
remediation: remediation
|
|
49
|
+
)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
RosettAi::Doctor.register(RosettAi::Doctor::Checks::StaleConfigNnccCheck)
|